Crear Aplicaciones JDBC Completas

Hasta ahora sólo hemos visto fragmentos de código. Más adelante veremos programas de ejemplo que son aplicaciones completas que podremos ejecutar.

El primer código de ejemplo crea la tabla COFFEES; el segundo inserta valores en la tabla e imprime los resultados de una petición. La terecera aplicación crea la tabla SUPPLIERS, y el cuarto la rellena con valores. Después de haber ejecutado este código, podemos intentar una petición que una las tablas COFFEES y SUPPLIERS, como en el quinto código de ejemplo. El sexto ejemplo de código es una aplicación que demuestra una transación y también muestra como configurar las posiciones de los parámetros en un objeto PreparedStatement utilizando un bucle for.

Como son aplicaciones completas, incluyen algunos elementos del lenguaje Java que no hemos visto en los fragmentos anteriores. Aquí explicaremos estos elementos brevemente.

Poner Código en una Definición de Clase

En el lenguaje Java, cualquier código que querramos ejecutar debe estar dentro de una definición de clase. Tecleamos la definición de clase en un fichero y a éste le damos el nombre de la clase con la extensión .java. Por eso si tenemos una clase llamada MySQLStatement, su definición debería estar en un fichero llamado MySQLStatement.java

Importar Clases para Hacerlas Visibles

Lo primero es importar los paquetes o clases que se van a utilizar en la nueva clase. Todas las clases de nuestros ejemplos utilizan el paquete java.sql (el API JDBC), que se hace visible cuando la siguiente línea de código precede a la definición de clase:
import java.sql.*;
El asterisco (*) indica que todas las clases del paquete java.sql serán importadas. Importar una clase la hace visible y significa que no tendremos que escribir su nombre totalmente cualificado cuando utilicemos un método o un campo de esa clase. Si no incluimos "import java.sql.*;" en nuestro código, tendríamos que escribir "java.sql." más el nombre de la clase delante de todos los campos o métodos JDBC que utilicemos cada vez que los utilicemos. Observa que también podemos importar clases individuales selectivamente en vez de importar un paquete completo. Java no requiere que importemos clases o paquetes, pero al hacerlo el código se hace mucho más conveniente.

Cualquier línea que importe clases aparece en la parte superior de los ejemplos de código, que es donde deben estar para hacer visibles las clases importadas a la clase que está siendo definida. La definición real de la clase sigue a cualquier línea que importe clases.

Utilizar el Método main()

Si una clase se va a ejecutar, debe contener un método static public main. Este método viene justo después de la línea que declara la clase y llama a los otros métodos de la clase. La palabra clave static indica que este método opera a nivel de clase en vez sobre ejemplares individuales de la clase. La palabra clave public significa que los miembros de cualquier clase pueden acceder a este método. Como no estamos definiendo clases sólo para ser ejecutadas por otras clases sino que queremos ejecutarlas, las aplicaciones de ejemplo de este capítulo incluyen un método main.

Utilizar bloques try y catch

Algo que también incluyen todas las aplicaciones de ejemplo son los bloques try y catch. Este es un mecanismo del lenguaje Java para manejar excepciones. Java requiere que cuando un método lanza un excepción exista un mecanismo que la maneje. Generalmente un bloque catch capturará la excepción y especificará lo que sucederá (que podría ser no hacer nada). En el código de ejemplo, utilizamos dos bloques try y dos bloques catch. El primer bloque try contiene el método Class.forName, del paquete java.lang. Este método lanza una ClassNotFoundException, por eso el bloque catch que le sigue maneja esa excepción. El segundo bloque try contiene métodos JDBC, todos ellos lanzan SQLException, por eso el bloque catch del final de la aplicación puede manejar el resto de las excepciones que podrían lanzarse ya que todas serían objetos SQLException.

Recuperar Excepciones

JDBC permite ver los avisos y excepciones generados por nuestro controlador de base de datos y por el compilador Java. Para ver las excepciones, podemos tener un bloque catch que las imprima. Por ejemplo, los dos bloques catch del siguiente código de ejemplo imprimen un mensaje explicando la excepción:
try {
	// Aquí va el código que podría generar la excepción.
	// Si se genera una excepción, el bloque catch imprimirá
	// información sobre ella.
} catch(SQLException ex) {
	System.err.println("SQLException: " + ex.getMessage());
}

try {
	Class.forName("myDriverClassName");
} catch(java.lang.ClassNotFoundException e) {
	System.err.print("ClassNotFoundException: "); 
	System.err.println(e.getMessage());
}
Si ejecutarámos CreateCOFFEES.java dos veces, obtendríamos un mensaje de error similar a éste:
SQLException: There is already an object named 'COFFEES' in the database.
Severity 16, State 1, Line 1
Este ejemplo ilustra la impresión del componente mensaje de un objeto SQLException, lo que es suficiente para la mayoría de las situaciones.

Sin embargo, realmente existen tres componentes, y para ser completos, podemos imprimirlos todos. El siguiente fragmento de código muestra un bloque catch que se ha completado de dos formas. Primero, imprime las tres partes de un objeto SQLException: el mensaje (un string que describe el error), el SQLState (un string que identifica el error de acuerdo a los convenciones X/Open de SQLState), y un código de error del vendedor (un número que es el código de error del vendedor del driver). El objeto SQLException, ex es capturado y se accede a sus tres componentes con los métodos getMessage, getSQLState, y getErrorCode.

La segunda forma del siguiente bloque catch completo obtiene todas las exepciones que podrían haber sido lanzada. Si hay una segunda excepción, sería encadenada a ex, por eso se llama a ex.getNextException para ver si hay más excepciones. Si las hay, el bucle while continúa e imprime el mensaje de la siguiente excecpción, el SQLState, y el código de error del vendedor. Esto continúa hasta que no haya más excepciones.

try {
	// Aquí va el código que podría generar la excepción.
	// Si se genera una excepción, el bloque catch imprimirá
	// información sobre ella.
} catch(SQLException ex) {
	System.out.println("\n--- SQLException caught ---\n");
	while (ex != null) {
		System.out.println("Message:   " + ex.getMessage ());
		System.out.println("SQLState:  " + ex.getSQLState ());
		System.out.println("ErrorCode: " + ex.getErrorCode ());
		ex = ex.getNextException();
		System.out.println("");
	}
}
Si hubieramos sustituido el bloque catch anterior en el Código de ejemplo 1 (CreateCoffees) y lo hubieramos ejecutado después de que la tabla COFFEES ya se hubiera creado, obtendríamos la siguiente información:
--- SQLException caught ---
Message:  There is already an object named 'COFFEES' in the database.
Severity 16, State 1, Line 1
SQLState: 42501
ErrorCode:   2714
SQLState es un código definido en X/Open y ANSI-92 que identifica la excepción. Aquí podemos ver dos ejemplos de códigos SQLState:
08001 -- No suitable driver
HY011 -- Operation invalid at this time 
El código de error del vendedor es específico de cada driver, por lo que debemos revisar la documentación del driver buscando una lista con el significado de estos códigos de error.

Recuperar Avisos

Los objetos SQLWarning son una subclase de SQLException que trata los avisos de accesos a bases de datos. Los Avisos no detienen la ejecución de una aplicación, como las excepciones; simplemente alertan al usuario de que algo no ha salido como se esperaba. Por ejemplo, un aviso podría hacernos saber que un privilegio que queriamos revocar no ha fue revocado. O un aviso podría decirnos que ha ocurrido algún error durante una petición de desconexión.

Un aviso puede reportarse sobre un objeto Connection, un objeto Statement (incluyendo objetios PreparedStatement y CallableStatement), o un objeto ResultSet. Cada una de esas clases tiene un método getWarnings, al que debemos llamar para ver el primer aviso reportado en la llamada al objeto. Si getWarnings devuelve un aviso, podemos llamar al método getNextWarning de SQLWarning para obtener avisos adicionales. Al ejecutar una sentencia se borran automáticamente los avisos de la sentencia anterior, por eso no se apilan. Sin embargo, esto significa que si queremos recuperar los avisos reportados por una sentencia, debemos hacerlo antes de ejecutar otra sentencia.

El siguiente fragmento de código ilustra como obtener información completa sobre los avisos reportados por el objeto Statement, stmt y también por el objeto ResultSet, rs:

Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("select COF_NAME from COFFEES");
while (rs.next()) {
	String coffeeName = rs.getString("COF_NAME");
	System.out.println("Coffees available at the Coffee Break:  ");					
	System.out.println("    " + coffeeName);
	SQLWarning warning = stmt.getWarnings();
	if (warning != null) {
		System.out.println("\n---Warning---\n");
		while (warning != null) {
			System.out.println("Message: " + warning.getMessage());
			System.out.println("SQLState: " + warning.getSQLState());
			System.out.print("Vendor error code: ");
			System.out.println(warning.getErrorCode());
			System.out.println("");
			warning = warning.getNextWarning();
		}
	}
	SQLWarning warn = rs.getWarnings();
	if (warn != null) {
		System.out.println("\n---Warning---\n");
		while (warn != null) {
			System.out.println("Message: " + warn.getMessage());
			System.out.println("SQLState: " + warn.getSQLState());
			System.out.print("Vendor error code: ");
			System.out.println(warn.getErrorCode());
			System.out.println("");
			warn = warn.getNextWarning();
		}
	}
}
Los avisos no son muy comunes, De aquellos que son reportados, el aviso más común es un DataTruncation, una subclase de SQLWarning. Todos los objetos DataTruncation tienen un SQLState 01004, indicando que ha habido un problema al leer o escribir datos. Los métodos de DataTruncation permiten encontrar en que columna o parámetro se truncaron los datos, si la ruptura se produjo en una operación de lectura o de escritura, cuántos bytes deberían haber sido transmitidos, y cuántos bytes se transmitieron realmente.

Ozito