Professional Documents
Culture Documents
Vamos a continuar con nuestro ejemplo sobre base de datos MySQL, aunque casi todo lo que
se dice aquí sirve para otros fabricantes.
Introducción a JDBC
La idea que está detrás de JDBC (Java Data Base Connectivity) es dar a los programadores un
API (Application Programming Interface) que les permita codificar de manera independiente al
fabricante del gestor de base de datos. JDBC tiene dos capas: por una parte el API JDBC y por
otra el controlador del fabricante, este último recibe las peticiones de JDBC y las traduce en
servicios internos del gestor de bases de datos:
1. El API JDBC que es el conjunto de clases que utiliza el programador. Dentro de este
API tenemos el administrador de controladores (Driver Manager) que recibe las
peticiones del programa Java y comunica con el controlador seleccionado, de manera
transparente para el programador.
2. El controlador recibe las ordenes y las traduce a ordenes directas al gestor de base de
datos.
Hay productos (escasísimos) para los que no hay controlador JDBC, pero si un controlador
ODBC. En estos casos se utiliza entre el administrador y el controlador ODBC un puente
(denominado normalmente JDBC-ODBC Bridge).
Tipos de controladores
Cuando accedas a la literatura especializada veras que se usa indistintamente la expresión
"tipo" o "nivel" (level). Un controlador de nivel 3 es lo mismo que decir un controlador de tipo 3:
1. Controlador que traduce JDBC a ODBC. SUN incluye uno en su JDK. Exige la
instalación y configuración de ODBC en la computadora cliente.
3. Controlador de Java puro que utiliza un protocolo de red (http, por ejemplo) para
comunicarse con un servidor de base de datos (por ello se denominan controladores de
protocolo no nativo). Este servidor traduce al lenguaje específico del producto. No
exige instalación en cliente.
Si no quiere modificar la variable CLASSPATH tendrá que copiar el archivo jar en el directorio
$JAVA_HOME/jre/lib/ext.
El nombre de la clase que implementa el controlador de MySQL es "com.mysql.jdbc.Driver" y
se encuentra dentro del archivo jar.
Volver al índice
Introducción
El siguiente objetivo es conectarse a la base de datos, para lo cual básicamente hay que
realizar dos pasos:
1. Registrar (cargar en memoria) el controlador. Esto se puede hacer de dos formas:
jdbc:Controlador://[host][,failoverhost...][:port]/[database][?
propertyName1][=propertyValue1][&propertyName2]
[=propertyValue2]...
package jdbc01;
import java.sql.DriverManager;
import java.sql.Connection;
import java.lang.ClassNotFoundException;
import java.sql.SQLException;
}
}
Aunque el código del ejemplo anterior está documentado, vamos a repasar algunos conceptos:
• Class.forName: si esta llamada falla (normalmente porque no se encuentra el archivo
del controlador), entonces se dispara una excepción ClassNotFoundException.
• De manera semejante, si el intento de conexión mediante
DriverManager.getConnection ha fallado se dispara la excepción SQLException. La
gestión de esta excepción verás que no sólo es necesaria en la conexión a la base de
datos, sino en otras circunstancias: definición de datos, manipulación de datos,
consulta, etc.
• Un aspecto que ya hemos comentado: puesto que getConnection() es static no resulta
necesario instanciar la clase DriverManager.
• Por último, no olvidar la higiene: cerramos la conexión. Ahora puede perecer algo
paranoico (no lo es). Pero además, a medida que las aplicaciones sean más complejas
la disciplina de cierre de conexiones es más necesaria.
El resultado final es el esperado, aparece por pantalla:
Para cargar en memoria esta información el proceso es muy simple, tanto que se
puede hacer todo en tres líneas:
1. Instanciamos la clase Properties: Properties props = new Properties();
2. Creamos un flujo de entrada para el archivo: FileInputStream in = new
FileInputStream("database.properties");
3. Cargamos el contenido en props: props.load(in);
• Obtener una propiedad en concreto del objeto props es muy simple, por ejemplo: String
url = props.getProperty("jdbc.url");
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.SQLException;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.sql.DriverManager;
import java.sql.Connection;
import java.lang.ClassNotFoundException;
import java.sql.SQLException;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
DriverManager.getConnection("jdbc:mysql://localhost/prueba")
trying
driver[className=com.mysql.jdbc.Driver,com.mysql.jdbc.Driver@5224ee]
getConnection returning
driver[className=com.mysql.jdbc.Driver,com.mysql.jdbc.Driver@5224ee]
Si tienes curiosidad, puedes provocar un error de conexión y verás que aparece descrito en el
archivo log.
Volver al índice
(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:
Principio del formulario
proactiv_prueba
Base:
proactiv_alumnos
Usuario:
*******
Password:
Entrar
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()+"/"
/
**********************************************************************
******
* 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 {
/******************************************************************
*********
* 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, "");
}
boolean cerrarConexion() {
try {
if ( con != null ) {
if (!con.isClosed())
con.close();
}
return true;
}
catch (SQLException e) {
return false;
}
}
Volver al índice
Servlets
Ramiro Lago (Octubre 2005)
En este capítulo nos centramos en el mundo de los servlets, de imprescindible conocimiento
para quien quiera entender y desarrollar aplicaciones empresariales. Para entender el papel de
los servlets dentro de la arquitectura J2EE puede consultar el capítulo dedicado a dicha
arquitectura.
2. Métodos y clases fundamentales (*). Usaremos los métodos init(), doPost(), doGet() y
destroy(). Dentro de init(), entre otras cosas, se obtienen parámetros de inicio
introducidos en web.xml. Dentro de doGet() se obtienen las propiedades de System.
4. Servlet que se conecta a una base de datos (utiliza JDBC) (*). Además presenta un
ejemplo del manejo de la clase Properties.
7. Uso de sendRedirect() (*). Un servlet no tiene necesariamente que generar una salida
en HTML, en muchas ocasiones realizan una acción e invocan (mediante
sendRedirect) a un segundo servlet. Además se muestra el uso de códigos de error en
la respuesta.
8. RequestDispatcher (*).
11. Servlets que utilizan JDBC y atributos de sesión (*). Un ejemplo de anidamiento de
servlets con HTML. Un servlet genera un formulario en HTML (lista de clientes), este
formulario invoca a un segundo servlet (ventas del cliente seleccionado) que da la
página de respuesta final. El acceso a la base de datos lo realizan clases DAO.
Además se maneja la sesión. Hay un ejemplo de uso de DecimalFormat.
14. Por medio de JNDI un servlet puede acceder a un pool de conexiones, para lo cual
debemos usar el interfaz DataSource.
Enlace a un tutorial de SUN sobre Servlets.
* Capítulos que contienen servlets de ejemplo.
Volver al índice
Después de haber estudiado las formas de conexión a la base de datos vamos a aprender a
realizar consultas a las tablas. Este capítulo da por supuesto que el lector ya tiene nociones
básicas de modelo relacional de datos y de SQL. Pero no nos preocupemos, la mecánica para
poder realizar consultas es bastante sencilla.
La estructura de la base de datos
Lo primero es conocer la estructura de la base de datos 'prueba':
• La primera tabla se llama cliente, representa la información esencial de cada cliente,
en donde el campo 'código' es la clave primaria. Ha sido creada con la siguiente
sentencia SQL:
•
• CREATE TABLE `cliente` (
• `codigo` char(10) NOT NULL default '',
• `nombre` char(20) default '',
• `ape1` char(20) default '',
• `ape2` char(20) default '',
• `edad` int(11) default '0',
• PRIMARY KEY (`codigo`)
• ) TYPE=InnoDB;
• La segunda tabla representa las ventas o facturación de cada cliente. La relación entre
la entidad 'cliente' y la entidad 'venta' es del tipo 1:N, es decir, a un cliente se le puede
asignar N ventas, pero una venta se refiere sólo a un cliente. Por tanto, el campo
'código' es una clave externa sobre la clave primaria 'cliente.codigo'. La tabla ha sido
creada por medio de:
•
• CREATE TABLE `venta` (
• `codigo` char(10) NOT NULL default '',
• `precio` float default '0',
• `coste` float default '0',
• INDEX(`codigo`),
• FOREIGN KEY (`codigo`) REFERENCES `cliente` (`codigo`) ON
DELETE CASCADE ON UPDATE CASCADE
• ) TYPE=InnoDB;
Con 'ON DELETE CASCADE' le ordenamos al gestor de base de datos que si se borra
un cliente, entonces se borrarán los registros de ventas de ese cliente. Con ON
UPDATE CASCADE indicamos que un cambio en el código del cliente
('cliente.codigo') implica reflejar el cambio en 'venta.codigo' con la finalidad de
mantener la integridad referencial.
Nota sobre MySQL: para manejar claves externas (foreign key) en MySQL por debajo de
versiones 5.1 es necesario que las tablas sean del tipo InnoDB. A partir de la versión 5.1 esta
característica es soportada por todos los tipos de tablas.
Los datos que tenemos en las tablas reflejan que hay tres clientes y hemos realizado cinco
ventas con ellos:
10. Hay que definir un bucle para obtener el resultado (fila a fila) de la consulta. Para ello
utilizamos la función ResulSet.next(). Ojo: el cursor del conjunto de resultados se situa
en la posición anterior a la primera fila, por tanto habrá que llamar a esta función para
obtener la primera fila:
11.
12. while ( rs.next() ) {
13. String res = rs.getString( "codigo" ) + ", " +
rs.getString( "nombre" ) + ", " + rs.getInt( "edad" );
14. System.out.println( res );
15. }
Dentro del bucle obtenemos los resultados de cada columna, por medio de funciones
getXXX( String ) de la clase ResulSet. El parámetro String representa el nombre de la
columna. En función del tipo de dato habrá que escoger el nombre de la función:
getInt() para enteros, getString para String, etc. En nuestro ejemplo, los datos
obtenidos se muestran por pantalla usando println().
20. Nos aseguramos del cierre de la conexión: con.close(). En nuestro caso, hemos
tomado precauciones. Puede ocurrir que la conexión haya sido correcta pero se
produce un error en una sentencia. Ante este hecho, hay que asegurarse que se
produce el cierre de la conexión. Para ello situamos en finally una llamada a nuestro
método cerrar_conexion():
21.
22. public static void cerrar_conexion( Connection con ) {
23. try {
24. if ( con != null )
25. if ( !con.isClosed() ) // Si no está cerrada, la
cierro
26. con.close();
27. }
28. catch (SQLException e) { e.printStackTrace(); }
29. }
Para nuestro ejemplo hemos creado una clase que llamamos 'consulta', cuyo constructor
recibe la conexión con la base de datos. La clase 'consulta' está especializada en la definición
y ejecución de las consultas SQL. El código completo del ejemplo lo puedes ver más abajo. En
la función ver_cliente() se implementa la sencilla consulta SELECT que acabamos de ver:
package jdbc01;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.lang.ClassNotFoundException;
import java.io.FileOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
/
**********************************************************************
**
Ejemplo de manejo de sentencias SELECT
**********************************************************************
***/
public class jdbc01_conexion4 {
public static void main(String[] args) {
Connection con = null;
try {
/*** Registro de driver ****/
Class.forName("com.mysql.jdbc.Driver");
/*****************************************************************
***
Me aseguro de que se cierra la conexión
******************************************************************
**/
public static void cerrar_conexion( Connection con ) {
try {
if ( con != null )
if ( !con.isClosed() ) // Si no está cerrada, la
cierro
con.close();
}
catch (SQLException e) { e.printStackTrace(); }
}
}
/*********************************************************
Clase en la que definimos y ejecutamos las sentencias SQL
**********************************************************/
class consulta {
Connection con;
consulta( Connection con ) {
this.con = con;
}
/*****************************************************************
**
Un sencillo SELECT de clientes, ordenados por nombre
******************************************************************
*/
void ver_cliente() {
try {
/***** Definir sentencia y ejecutarla ********/
String orden_SQL = "SELECT cliente.codigo, cliente.nombre,
cliente.edad " +
"FROM cliente ORDER BY cliente.nombre";
Statement sentencia = con.createStatement();
ResultSet rs = sentencia.executeQuery( orden_SQL );
/*****************************************************************
**
Un poco más de complejidad que el anterior
Un sencillo SELECT de clientes, ordenados por nombre y
especificando en WHERE
una edad mínima y máxima
******************************************************************
*/
void ver_cliente( int edad_min, int edad_max ) {
try {
/***** Definir sentencia y ejecutarla ********/
String orden_SQL = "SELECT cliente.codigo, cliente.nombre,
cliente.edad " +
"FROM cliente " +
"WHERE cliente.edad BETWEEN " + edad_min
+ " AND " + edad_max +
" ORDER BY cliente.nombre";
Statement sentencia = con.createStatement();
ResultSet rs = sentencia.executeQuery( orden_SQL );
/*****************************************************************
**
Un poco más de complejidad que el anterior
El SELECT anterior y además con un JOIN sobre la tabla venta para
ver
las ventas de cada cliente.
******************************************************************
*/
void ver_cliente_venta( int edad_min, int edad_max ) {
try {
/***** Definir sentencia y ejecutarla ********/
String orden_SQL = "SELECT cliente.codigo, cliente.nombre,
cliente.edad, " +
"venta.precio, venta.coste " +
"FROM cliente, venta " +
"WHERE cliente.codigo = venta.codigo AND
" +
"cliente.edad BETWEEN " + edad_min + "
AND " + edad_max +
" ORDER BY cliente.nombre";
Statement sentencia = con.createStatement();
ResultSet rs = sentencia.executeQuery( orden_SQL );
/*****************************************************************
**
Un poco más de complejidad que el anterior
Hago cálculos (sum) y utilizo la expresión AS.
Agrupo las ventas por cliente.
******************************************************************
*/
void ver_beneficio_cliente() {
try {
/***** Definir sentencia y ejecutarla ********/
String orden_SQL = "SELECT cliente.nombre, cliente.codigo,
sum(venta.precio) " +
"AS ingresos , sum(venta.coste) AS costes,
sum(venta.precio)-sum(venta.coste) AS beneficio "+
"FROM cliente, venta WHERE cliente.codigo =
venta.codigo "+
"GROUP BY cliente.nombre";
}
catch (SQLException e) { e.printStackTrace(); }
}
}
El resultado obtenido es:
• ver_cliente_venta( int edad_min, int edad_max ): en esta función se listan todos los
clientes cuya edad está entre edad_min y edad_max. Pero lo más interesante es que
se listan las ventas de CADA cliente (recordar que un cliente puede tener N ventas).
Para listar las ventas se especifica la relación que hay entre las dos tablas por medio
de la clausula WHERE cliente.codigo = venta.codigo. De esta forma seleccionamos
las ventas ASOCIADAS o REFERENCIADAS a su cliente correspondiente.
ResultSet rs =
DriverManager.getConnection( "jdbc:mysql://localhost/prueba","root",
"palabra" ).createStatement().executeQuery( "SE
LECT cliente.codigo, cliente.nombre, cliente.edad " +
"FROM cliente ORDER BY cliente.nombre" );
Os lo dice una persona que tiene experiencia en equipos de desarrollo de software: los
mejores programas no son los más breves (menos líneas de código), sino los más
legibles dentro de un contexto de eficiencia. Imaginemos el absurdo de un arquitecto que
realiza un plano muy pequeño y fácil de transportar, pero que no puede comprender el resto del
equipo (aparejador, maestro de obra, etc) ¿Es esto inteligente? Resulta evidente que la
programación es una actividad colectiva, inscrita dentro de un grupo y sus métodos y procesos
están destinados a favorecer la "sociabilidad" de los resultados.
Para obtener los resultados de las consultas hemos usado métodos getXXX( String
nombre_columna ). Esto es bastante claro, pero que se tranquilicen todos los maniacos de la
confusión, podemos hacerlo un poco menos claro, obteniendo los resultados por medio de
funciones getXXX( int numero_columna ), donde el argumento indica el orden de columna
dentro de la consulta (empezando desde uno, no desde cero).
Las funciones getXXX( int numero_columna ) no tienen mucho sentido cuando el
programador conoce la estructura y relaciones de los datos. Sin embargo, en ocasiones hay
que programar aplicaciones que accedan a diversas bases de datos y/o acceder a bases de
datos desconocidas. En este contexto tiene sentido el uso de dichas funciones. También
resultan útiles para obtener metadatos, como veremos en el capítulo dedicado a los metadatos.
Volver al índice
Servlets y JDBC
Introducción
Vamos a tratar una serie de aspectos de interés:
1. Los servlets hacen en init() la carga del driver y en doPos()/doGet se conectan a la
base de datos y ejecutan una consulta que se escribe sobre la página.
3. Vamos a "anidar" servlets, dicho de otro modo: un servlet tiene como salida un
formulario, que invoca a otro servlet. El primer servlet imprime un combobox
('desplegable') con todos los clientes, dando la opción al usuario de seleccionar uno. El
segundo muestra las ventas del cliente seleccionado.
4. Además vamos a manejar la sesión. Es necesario que las llamadas a los servlets
mantengan cierta continuidad, como ocurre en una web comercial, donde pasamos por
las páginas del catálogo y se mantiene información del carrito de la compra o de
cliente. Para mantener información a lo largo de sucesivas llamadas necesitamos usar
atributos de sesión.
....
<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>
....
proactiv_alumnos
Usuario:
*******
Password:
Entrar
El servlet FormClientes
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:
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);
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());
....
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"));
dc.setIdentificacion(request.getParameter("login"),
request.getParameter("password"));
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:
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.
Transferencia de datos (session y request)
Pensemos en el paso de información del primer al segundo servlet. Analizando:
• En la sesión se transporta la base de datos, login y password. Estos datos serán leidos
por el segundo servlet.
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()):
FormVentas
El servlet que debe obtener las ventas de un cliente seleccionado recibe información por dos
medios:
1. Recibe el código de cliente seleccionado como un parámetro de request.
2. Recibe como atributos de la sesión el nombre de la base de datos, login y password.
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:
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);
}
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
dv.setIdentificacion( (String)sesion.getAttribute("login"),
(String)sesion.getAttribute("password"));
if ( vecVentas.size() == 0)
UtilGeneral.imprimir( out, "No hay
ventas registradas", true, true );
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 );
}
}
}
Volver al índice
Después de haber aprendido lo sencillo que es implementar una sentencia SELECT vamos a
estudiar INSERT Y UPDATE.
INSERT
La diferencia más importante respecto a SELECT es que usamos la función executeUpdate().
En el siguiente ejemplo damos por supuesto que se han creado previamente los objetos de la
clase Connection (con) y Statement (sentencia):
UPDATE
En el caso de UPDATE vamos a realizar un ejemplo en el que incrementamos el valor de las
ventas un 10%:
....
update_venta( 1.1 );
....
/***** UPDATE de registros, devuelve el número de registros
actualizados ****/
int update_venta( double incremento ) {
try {
String orden_SQL = "UPDATE venta SET precio =
precio * " + incremento;
Statement sentencia = con.createStatement();
int fila = sentencia.executeUpdate(orden_SQL);
sentencia.close();
return fila;
}
catch (SQLException e) {
e.printStackTrace();
return 0;
}
}
Si deseasemos realizar una actualización selectiva (por ejemplo, para las ventas mayores de
4500), tendremos que utilizar la clausula WHERE.
Volver al índice
Sentencias preparadas
Introducción
Supongamos que hay un tipo de sentencia que se repite con frecuencia. En nuestro
ejemplo podemos necesitar un informe de los clientes en función de su edad:
• select nombre,edad from cliente where edad >= 18 and edad <= 25
• select nombre,edad from cliente where edad >= 20 and edad <= 30
El problema de esto es que para todas y cada una de estas sentencias el gestor de bases de
datos debe analizar y compilar. Esto supone un consumo innecesario de recursos. Podriamos
tener preparado un esquema de sentencia y parametrizarlo en función de las necesiades
del usuario. En resumen, las sentencias preparadas resultan más eficientes y más cómodas.
Un ejemplo
En este ejemplo vamos a preparar un esquema de sentencia que es parametrizable, para
obtener los clientes en función de su edad. Los parámetros se indican con un interrogante (?):
st.close();
Con el primer argumento de setInt() indicamos el número de parámetro que estamos
definiendo: el primero y el segundo
Volver al índice
METADATOS
¿Meta qué?
Sabemos que es un dato (por ejemplo, el número 32 o la palabra "Juan"). ¿Qué es un
metadato? Cualquier información al respecto del dato que nos ayuda a comprenderlo o
utilizarlo. Los primeros metadatos de interés son aquellos que tienen que ver con la estructura
de datos, como el nombre y tipo de una columna, por ejemplo, el 32 es uno de los datos de la
columna 'edad', del tipo int, que está en la tabla 'cliente'. Pero esto es trivial, metadatos
propiamente dichos son información sobre el origen de datos de la columna o tabla, una
explicación de la columna o tabla, restricciones, la descripción del cálculo al que obedece, etc.
Por ejemplo, la columna 'edad' puede tener como metadato una descripción del modo en que
se ha calculado: 'fecha_actual - tabla1.fecha_nacimiento'.
El manejo de metadatos en JDBC tiene interés cuando tenemos que realizar aplicaciones que
acceden a cualquier base de datos o ejecuten cualquier sentencia dada por el usuario.
Ejecutar cualquier consulta (query)
Vamos a realizar una sencilla función que ejecuta cualquier SELECT que el usuario introduzca
por teclado.En nuestro ejemplo tenemos que observar:
• Mediante una llamada a nuestro método String obt_sentencia() obtenemos por medio
de teclado la sentencia SQL:
•
• static String obt_sentencia() {
• try {
• /* Creo el objeto 'entrada', es un lector de
entradas por teclado */
• BufferedReader entrada = new BufferedReader(new
InputStreamReader(System.in));
•
• /****** Pido la sentencia por teclado ******/
• System.out.println( "Teclee consulta:");
•
• return entrada.readLine();
• }
• catch (IOException e) { e.printStackTrace(); return
null; }
• }
package jdbc01;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.lang.ClassNotFoundException;
import java.io.*;
try {
/*** Registro de driver ****/
Class.forName("com.mysql.jdbc.Driver");
s.close();
}
catch( ClassNotFoundException e )
{ e.printStackTrace(); }
catch (SQLException e) { e.printStackTrace(); }
return entrada.readLine();
}
catch (IOException e) { e.printStackTrace(); return
null; }
}
/*************************************************************
Me aseguro de que se cierra la conexión
*************************************************************/
public static void cerrar_conexion( Connection con ) {
try {
if ( con != null )
if ( !con.isClosed() ) // Si no está
cerrada, la cierro
con.close();
}
catch (SQLException e) { e.printStackTrace(); }
}
class consulta_x {
Connection con;
consulta_x( Connection con ) {
this.con = con;
}
System.out.print( rs.getString(i) );
}
}
}
catch (SQLException e) { e.printStackTrace(); }
}
}
Además podemos obtener el tamaño de cada columna por medio de:
package jdbc01;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.lang.ClassNotFoundException;
/**********
Obtengo el ResultSet de las tablas:
El tercer argumento puede especificar el nombre de la
tabla
El cuarto argumento especifica los tipos (TABLE, VIEW,
etc.). null: todos
*********/
ResultSet rs = dbmd.getTables( null, null, null, new
String[]{"TABLE"} );
dbmd.supportsResultSetType(ResultSet.TYPE_SCROLL_SENSITIVE));
System.out.println( "Además actualiza BD: " +
dbmd.supportsResultSetConcurrency(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE));
/******************
ResultSet para tratar cada tabla: la fila tiene las
características
de la tabla (base de datos, esquema, nombre, tipo y
descripción)
*****************/
while ( rs.next() ) {
rs.close();
}
catch( ClassNotFoundException e ) { e.printStackTrace(); }
catch (SQLException e) { e.printStackTrace(); }
/*** Haya excepción o no, tengo que cerrar la conexión ***/
finally {
cerrar_conexion( con );
}
}
/*************************************************************
Me aseguro de que se cierra la conexión
*************************************************************/
public static void cerrar_conexion( Connection con ) {
try {
if ( con != null )
if ( !con.isClosed() ) // Si no está
cerrada, la cierro
con.close();
}
catch (SQLException e) { e.printStackTrace(); }
}
}
>
Observar que podemos obtener diferentes clases de metadatos:
1. En nuestra aplicación lo primero es obtener acceso a metadatos de la base de datos
(DatabaseMetaData):
2.
3. DatabaseMetaData dbmd = con.getMetaData();
System.out.print("\t" +
mdata_sel.getColumnLabel(num_col_sel));
System.out.print(" (Size=" +
mdata_sel.getColumnDisplaySize(num_col_sel) +")");
¿Cómo obtenemos todos los campos de una tabla? En la función mostrar_cols_tabla() la
sentencia SQL "SELECT * ..." nos permite acceder a todos los campos de la tabla.
Volver al índice
• En el primer panel del JTabbedPane nos pide los datos para conectarse a una base de
datos, especificando la tabla. Este applet le permite ver casi cualquier tabla de la base
de datos.
Las primeras opciones de driver y host hacen referencia a recursos de un servidor local. Las
segundas opciones se refieren a un servidor remoto. En su caso se encuentra ante un applet
remoto, por tanto debe escoger el driver del servidor: org.gjt.mm.mysql.Driver; además
debería escoger el host remoto, es decir, jdbc:mysql://proactiva-calidad.com:3306. Con ello
hacemos referencia al servidor de la base de datos (no al servidor web), indicando el puerto
(3306) asignado al gestor (mysql) de base de datos. Escriba como password la palabra 'nebrija'
(sin comillas y en minúsculas).
Si cambia la tabla y pone 'cliente', visualizará los clientes de las operaciones de venta.
Si el applet es local no tiene problemas en acceder a la base de datos local. Por lo mismo, si el
applet está en el servidor no tendrá problemas en acceder a la base de datos remota (del
servidor). El problema está en las situaciones mixtas:
• Applet local accediendo a base de datos remota.
• Applet de servidor accediendo a base de datos local.
¿Por que estas situaciones son problematicas a priori? El applet esta limitado por severas
restricciones de seguridad. No puede acceder a resursos ajenos a su espacio de trabajo. Vea
el capitulo dedicado a la seguridad en la sección de applets, donde se expone una solución a
estos problemas.
Diseño del applet: patrón modelo-vista
Se aplica el patrón modelo-vista:
• Modelo: responsable del tratamiento de datos. Lo implentamos en modelo.java.
• Vista: responsable del interfaz y gestión de eventos. En nuestro ejemplo lo
implementamos en vista.class, donde se encuentra el JApplet.
Código fuente:
• modelo.java
• vista.java
El modelo
Para más información sobre las tablas y sus modelos de datos: Tutorial de SUN sobre JTable
Vea que la clase modelo hereda de AbstractTableModel, más abajo explicaremos las
razones. Lo que importa para empezar es ver que los atributos son en su mayoría clases JDBC
que se instancian durante la conexión a la base de datos y la carga de datos:
/*****************************************************************
****
Conecta a la base de datos y carga los datos. Devuelve true si
ha ido bien.
******************************************************************
****/
public boolean conexion( String driver, String host_basedatos,
String tabla, String login,
String pwd ) {
/*****************************************************************
********
* Carga el driver. Devuelve:
* - true: todo ha ido bien
* - false: hay un error. El mensaje de error se registra en
atributo mensaje_error
*****************************************************************
********/
private boolean cargarDriver( String driver) {
try {
Class.forName( driver );
return true;
}
catch( ClassNotFoundException e ) {
mensaje_error = new String( "No encuentra el driver. " +
e.getMessage() );
return false;
}
}
....
Lo primero que debe hacer la clase modelo es cargar el driver del gestor de base de datos y a
continuación conectarse a la base de datos para poder realizar la consulta. Esto último implica
la ejecución de la sentencia SELECT. Estas funciones no tienen nada especial, ya hemos visto
en otros capítulos como realizar la conexión y ejecución. Un ejemplo de código para la
conexión:
try {
con = DriverManager.getConnection( base, login, pwd );
return true;
}
catch (SQLException e) {
mensaje_error = new String( "Error de conexión. " +
e.getMessage() );
return false;
}
}
A la hora de la consulta el trabajo es sencillo, aunque en nuestro ejemplo hay algunas
particularidades:
• Manejamos un cursor (Resultset) que debe admitir desplazamiento. Esto depende del
gestor de base de datos. En nuestro caso hemos solicitado un cursor o ResulSet que
admita desplazamiento (aunque sea insensible a cambios de otros usuarios):
TYPE_SCROLL_INSENSITIVE. Además solicitamos un cursor que nos permita
cambiar los datos: CONCUR_UPDATABLE. En nuestro caso no modificamos los
datos, pero se muestra a efectos pedagógicos. No todos los gestores de bases de
datos tienen esta conjunción de características. Por ejemplo, Access no contempla esta
capacidad. Volveremos más adelante a hablar de los cursores:
• El método actualizar() puede ver que ejecuta la sentencia SELECT, donde el nombre
de la tabla esta parametrizado (puede leer casi cualquier tabla de la base de datos).
return resultado;
}
catch (SQLException e) {
mensaje_error = new String( e.getMessage() );
return false;
}
}
/**********************************************************
Si la base de datos admite scroll, devuelve true
Si no: registra el error en atributo mensaje_error y devuelve
false
***********************************************************/
private boolean admite_scroll() throws SQLException {
return true;
}
Una vez que hemos solventado el problema de la conexión a base de datos y de la ejecución
de la sentencia SELECT, veamos cómo programar la tabla (JTable).Varios controles Swing
(entre ellos JTable) están diseñados para que el programador diferencie dos componentes:
• La vista (por ejemplo la clase JTable), que es responsable de los aspectos de
visualización.
• El modelo de datos del JTable (por ejemplo la clase AbstractTableModel),
responsable del tratamiento de datos.
Se puede trabajar con una JTable sin un modelo, al principio parece que incluso puede
ahorrarse líneas de código, pero es una falsa impresión, ya que a a la larga la reusabilidad,
eficiencia y legibilidad de los JTable mejora si usa un modelo abstracto. Los modelos de datos
de JTable heredan de AbstractTableModel. El constructor de la tabla (JTable) nos permite
pasar como argumento una clase AbstractTableModel:
3. Se lo asigna a la JTable:
En nuestro caso queremos además que si la conexión ha tenido éxito, se cambie de forma
automática al panel de consulta:
pnlTab.setSelectedComponent( pnlConsulta );
Por el sólo hecho de utilizar este interfaz debemos implementar el método actionPerformed().
Para que este método sea llamado como consecuencia de los eventos debemos indicar que el
listener u oyente del evento será nuestro applet (this):
btnConexion.addActionListener( this );
Volver al índice
Introducción
En capítulos anteriores hemos podido obtener un conjunto de resultados a partir de una
consulta. Además hemos visto como nos podemos desplazar por el conjunto de resultados. A
continuación veremos como se pueden modificar los datos del conjunto y volcar las
modificaciones en la base de datos.
Lo primero es comprobar si nuestro gestor de base de datos nos permite modificar conjuntos
de resultados. Existen unas constantes static en la clase ResultSet que identifican el ResulSet
en función de si pueden o no actualizar la base de datos:
CONCUR_READ_ONLY El ResultSet no puede modificar la base de datos.
CONCUR_UPDATABLE El ResultSet puede modificar la base de datos.
Una vez que hayamos establacido la conexión, tenemos que obtener un objeto de la clase
Statement que admita actualizaciones de ResultSet. Para ello tenemos una versión de
createStatement que dispará una excepción del tipo SQLException, en el caso de que la
base de datos no permita el tipo señalado en resultSetType o resultSetConcurrency:
public Statement createStatement(int resultSetType,
int resultSetConcurrency) throws SQLException
Como ejemplo:
String orden_SQL = "SELECT codigo, nombre FROM cliente ORDER BY
nombre";
Statement sentencia =
con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = sentencia.executeQuery( orden_SQL
);
Lo anterior es una forma sencilla y adecuada de tener un ResultSet actualizable. Veamos a
continuación una forma erronea de crear un ResultSet actualizable. Si el objeto de tipo
Statement se ha obtenido con la llamada a createStatement sin argumentos:
Statement sentencia = con.createStatement( );
Nos puede ocurrir que al modificar el ResultSet:
/*** Nos ponemos en el primero y lo modificamos ***/
rs.first();
rs.updateString(
"nombre", "Joaquín");
rs.updateRow();
Se dispare una excepción. ¿Por qué? Estamos modificando un conjunto de resultados, pero el
Statement del que proviene no admite esta capacidad:
com.mysql.jdbc.NotUpdatable: Result Set not updatable.This result set
must come
from a statement that was created
with a result set type of ResultSet.CONCUR_UPDATABLE
También podemos saber si el conjunto permite actualización mediante un método de ResulSet:
public int getConcurrency()
Que devuelve la capacidad del ResultSet en la forma de las constantes antes descritas. Otro
método para averiguar la capacidad de un ResultSet implica el manejo de metadatos, que ya
hemos visto en otros capítulos:
boolean DatabaseMetaData.supportsResultSetConcurrency(int type,
int concurrency)
throws SQLException
Dispará una excepción del tipo SQLException, en el caso de que la base de datos no permita
el tipo señalado en resultSetType o resultSetConcurrency.
Hay que tener en cuenta que no todas las consultas que nos devuelven un conjunto de
resultados nos permiten actualización (aunque el gestor de base de datos si lo permita). La
razón de esto puede ser que la consulta implique a varias tablas y que no estén enlazadas por
el enlace de clave primaria - clave externa o que incluyendo varias tablas la consulta no incluya
las claves primarias.
Actualizar
Unas sencillas líneas de ejemplo:
/***** Definir sentencia y ejecutarla ********/
String orden_SQL = "SELECT
codigo, nombre FROM cliente ORDER BY nombre";
Statement sentencia = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet
rs = sentencia.executeQuery( orden_SQL );
rs.updateRow();
Lo primero es obtener un objeto Statement por medio de crateStatement; este objeto permite
scroll (desplazamiento) y actualización . A continuación nos situamos mediante first() en la fila
que queremos modificar (la primera) y realizamos el cambio de la columna "nombre" con
updateString(), indicando en su segundo argumento el nuevo valor ("Joaquín"). Hay una
versión de updateXXX() para cada tipo de campo: String, Double, etc.
Es importante destacar que la modificación se refiere al ResultSet, concretamente a la fila
actual del ResultSet, y si nos movemos a otra fila los cambios se perderán, a menos que antes
del desplazamiento llames a updateRow(). Unicamente updateRow() vuelca los cambios a la
base de datos. Con el método cancelRowUpdates() cancelamos las modificaciones DE LA
FILA ACTUAL.
Inserción y borrado
Después de haber aprendido la actualización, la inserción no resulta difícil. Lo primero es
desplazar el cursor a una posición especial, una pseudofila en blanco, mediante una llamada a
moveToInsertRow() de la clase ResultSet. A continuación usamos updateXXX( String
nombre_columna, nuevo_valor ) para dar valores a las columnas:
rs.moveToInsertRow();
rs.updateString( "codigo", "OJD33");
rs.updateString(
"nombre", "Pedro Juan");
rs.updateInt( "edad", 34 );
rs.insertRow();
// Guardar en base de datos
rs.moveToCurrentRow(); // Volvemos
a la posición anterior a hacer moveToInsertRow
Con moveToCurrentRow() movemos el cursor a la posición anterior a la llamada a
moveToInsertRow. En nuestro ejemplo la posición que ocupa el registro insertado depende de
la cláusula ORDER BY de la consulta.
El borrado es extremadamente sencillo: el método deleteRow() borra la fila activa tanto del
ResultSet como de la base de datos.
Sensatez
El manejo de ResultSet con actualización es una herramienta sencilla y muy utilizada. Pero
conviene hacer alguna consideración:
1. Resulta conveniente en aquellas aplicaciones en donde el usuario puede modificar de
forma inmediata y extensa los registros. Por ejemplo, si presentas una tabla (JTable) y
das al usuario la libertad para modificar cualquier fila. En cualquier otro caso es más
eficiente usar las sentencias SQL INSERT, UPDATE o DELETE.
2. Tener en cuenta las desventajas comparativas respecto a usar sentencias SQL con
executeUpdate(): con un ResultSet hay que ejecutar una sentencia SELECT, localizar
la fila adecuada y realizar la actualización. Esto puede ser menos eficiente que ejecutar
directamente una sentencia SQL.
Volver al índice
<env-entry>
<env-entry-name>ejemplos/conexion</env-entry-name>
<env-entry-type>javax.sql.DataSource</env-entry-type>
<env-entry-auth>Container</env-entry-auth>
</env-entry>
Debemos tener en CATALINA_HOME/commons/lib (o el CLASSPATH) el driver JDBC: por
ejemplo, mysql-connector-java-3.1.6-bin.jar (MySQL)
El método init()
Con el servlet lo primero que hacemos es crear una fuente de datos:
import javax.servlet.*;
import javax.servlet.http.*;
import java.sql.*;
import javax.sql.*;
import java.io.*;
import javax.naming.*;
/******************************************************************
**************
* INIT: creo una fuente de datos (DataSource)
******************************************************************
**************/
public void init(ServletConfig config) throws ServletException {
try {
Context contextoInicial = new InitialContext(); //
Equivalente: new InitialContext(null).
Context contexto = (Context)
contextoInicial.lookup("java:comp/env");
fuenteDatos = (DataSource)
contexto.lookup( "ejemplos/conexion");
}
catch(NameNotFoundException e) { // Hija de
NamingException
errorInicial = new String(e.toString());
}
catch(Exception e) {
errorInicial = new String(e.toString());
}
}
En doGet()
El servlet en doGet() solicita una conexión a la fuente de datos por medio de getConnection().
Observar que el objeto Connection es local al método, ya que lo declaramos para recibir una
conexión del pool y luego lo devolvemos al pool. Decimos "devolver" ya que con el uso del
pool el programador no cierra conexiones, sino que las devuelve al pool.
try
{
//// Si no hay error en init(): solicito conexión al pool
if ( errorInicial == null ) {
con = fuenteDatos.getConnection();
}
}
catch( Exception e) {
errorPeticion = new String(e.toString());
}
finally {
//// Escribo página con resultado de la búsqueda
out.println("<html>");
out.println("<head><title>Ejemplo de Servlet y
JNDI</title></head>");
out.println("<body bgcolor=\"#FFFF9D\"><font
color=\"#000080\" FACE=\"Arial,Helvetica,Times\" SIZE=2>");
out.println("<CENTER><H3>Usa JNDI para recuperar
propiedades de web.xml</H3></CENTER><HR>");
out.println("<P>init:" + ((errorInicial==null) ? "No hay
error" : errorInicial) + "</P>");
out.println("<P>request:" + ((errorPeticion==null) ? "No hay
error" : errorPeticion) + "</P>");
out.println("<p>Conexión: " + ((con==null) ? "No" :
con.toString()) + "</p>");
while ( rs.next() ) {
out.println( "<li>" + rs.getString(1) + ", " +
rs.getString(2) + "</li>");
}
rs.close();
sentencia.close();
con.close(); // Se devuelve la conexión al pool
}
catch (Exception e) {
out.println( "<li>Error en la consulta</li>");
//// Asegurar que result sets and statements son cerrados,
//// y que la conexión retorna al pool
if (rs != null) {
try { rs.close(); } catch (SQLException sqle) {}
}
if (sentencia != null) {
try { sentencia.close(); } catch (SQLException sqle) {}
}
if (con != null) {
try { con.close(); } catch (SQLException sqle) {}
}
}
out.println("</ul></font></body></html>");
}
}
La consulta no tiene nada de particular. Se crea el objeto Statement a partir de la conexión
obtenida del pool. Observar que cerramos (close) el ResulSet, Statement y Connection
(aunque en realidad ya hemos dicho que no se destruye la conexión, sino que se devuelve
al pool). Una forma segura de devolver la conexión al pool:
Configurar server.xml
A continuación los parámetros de configuración de server.xml. Es importante observar que el
orden puede ser relevante en función de la versión de servidor de aplicacicones. Observar
que name de ResorceParams debe ser el mismo que el de Resource y a su vez debe
coincidir con env-entry-name de web.xml. Las expresiones que ve a continuación se deben de
colocar dentro de tu contexto (etiqueta Context) del archivo server.xml
<Context>
....
<Resource
name="ejemplos/conexion"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="usuario"
password="pwd"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/base_datos?autoReconnect=true"
/>
</Context>
Explicación de algunos parámetros:
• Use the removeAbandonedTimeout parameter to set the number of seconds a dB
connection has been idle (libre o desocupada) before it is considered abandoned. Por
defecto: 300
• maxActive: Maximum number of dB connections in pool. Make sure you configure your
mysqld max_connections large enough to handle all of your db connections. Set to 0 for
no limit.
• maxIdle: Maximum number of idle dB connections to retain in pool. Set to 0 for no limit.
• maxWait: Maximum time to wait for a dB connection to become available in ms, in this
example 10 seconds. An Exception is thrown if this timeout is exceeded. Set to -1 to
wait indefinitely.
• url: The JDBC connection url for connecting to your MySQL dB. The
autoReconnect=true argument to the url makes sure that the mm.mysql JDBC Driver
will automatically reconnect if mysqld closed the connection. mysqld by default closes
idle connections after 8 hours.
Volver al índice