Acceso A Bases de Datos JDBC
Acceso A Bases de Datos JDBC
Acceso A Bases de Datos JDBC
Autor: Sun
Traductor: Juan Antonio Palos (Ozito)
Aunque los objetos PreparedStatement se pueden utilizar con sentencias SQL sin
parámetros, probablemente nosotros utilizaremos más frecuentemente sentencias con
parámetros. La ventajA de utilizar sentencias SQL que utilizan parámetros es que
podemos utilizar la misma sentencia y suministrar distintos valores cada vez que la
ejecutemos. Veremos un ejemplo de esto en las página siguientes.
Necesitamos suministrar los valores que se utilizarán en los luegares donde están las
marcas de interrogación, si hay alguno, antes de ejecutar un objeto PreparedStatement.
Podemos hacer esto llamado a uno de los métodos setXXX definidos en la clase
PreparedStatement. Si el valor que queremos sustituir por una marca de interrogación
es un int de Java, podemos llamar al método setInt. Si el valor que queremos sustituir
es un String de Java, podemos llamar al método setString, etc. En general, hay un
método setXXX para cada tipo Java.
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
Después de que estos valores hayan sido asignados para sus dos parámetros, la
sentencia SQL de updateSales será equivalente a la sentencia SQL que hay en string
updateString que utilizando en el ejemplo anterior. Por lo tanto, los dos fragmentos de
código siguientes consiguen la misma cosa.
Código 1.
Código 2.
Una vez que a un parámetro se ha asignado un valor, el valor permanece hasta que lo
resetee otro valor o se llame al método clearParameters. Utilizando el objeto
PreparedStatement: updateSales, el siguiente fragmento de código reutiliza una
sentencia prepared después de resetar el valor de uno de sus parámetros, dejando el otro
igual.
updateSales.setInt(1, 100);
updateSales.setString(2, "French_Roast");
updateSales.executeUpdate();
// changes SALES column of French Roast row to 100
updateSales.setString(2, "Espresso");
updateSales.executeUpdate();
// changes SALES column of Espresso row to 100 (the first
// parameter stayed 100, and the second parameter was reset
// to "Espresso")
Normalmente se codifica más sencillo utilizando un bucle for o while para asignar
valores de los parámetros de entrada.
PreparedStatement updateSales;
String updateString = "update COFFEES " +
"set SALES = ? where COF_NAME like ?";
updateSales = con.prepareStatement(updateString);int [] salesForWeek =
{175, 150, 60, 155, 90};
String [] coffees = {"Colombian", "French_Roast", "Espresso",
"Colombian_Decaf", "French_Roast_Decaf"};
int len = coffees.length;
for(int i = 0; i < len; i++) {
updateSales.setInt(1, salesForWeek[i]);
updateSales.setString(2, coffees[i]);
updateSales.executeUpdate();
}
Cuando el propietario quiera actualizar las ventas de la semana siguiente, puede utilizar
el mismo código como una plantilla. Todo lo que tiene que haces es introducir las
nuevas cantidades en el orden apropiado en el array salesForWeek. Los nombres de
cafés del array coffees permanecen constantes, por eso no necesitan cambiarse. (En una
aplicación real, los valores probablemente serían introducidos por el usuario en vez de
desde un array inicializado).
Siempre que executeQuery devuelve un objeto ResultSet que contiene los resultados
de una petición al controlador de la base datos, el valor devuelto por executeUpdate es
un int que indica cuántas líneas de la tabla fueron actualizadas. Por ejemplo, el siguiente
código muestra el valor de retorno de executeUpdate asignado a la variable n.
updateSales.setInt(1, 50);
updateSales.setString(2, "Espresso");
int n = updateSales.executeUpdate();
// n = 1 because one row had a change in it
Cuando el método executeUpdate es utilizado para ejecutar una sentecia DDL, como la
creación de una tabla, devuelve el int: 0. Consecuentemente, en el siguiente fragmento
de código, que ejecuta la sentencia DDL utilizada pra crear la tabla COFFEES, n
tendrá el valor 0.
int n = executeUpdate(createTableCoffees); // n = 0
Observa que cuando el valor devuelto por executeUpdate sea 0, puede significar dos
cosas: (1) la sentencia ejecutada no ha actualizado ninguna fila, o (2) la sentencia
ejecutada fue una sentencia DDL.
Utilizar Uniones
Algunas veces necesitamos utilizar una o más tablas para obtener los datos que
queremos. Por ejemplo, supongamos que el propietario del "The Coffee Break" quiere
una lista de los cafés que le compra a Acme, Inc. Esto implica información de la tabla
COFFEES y también de la que vamos a crear SUPPLIERS. Este es el caso en que se
necesitan los "joins" (unión). Una unión es una operación de base de datos que relaciona
dos o más tablas por medio de los valores que comparten. En nuestro ejemplo, las tablas
COFFEES y SUPPLIERS tienen la columna SUP_ID, que puede ser utilizada para
unirlas.
Antes de ir más allá, necesitamos crear la tabla SUPPLIERS y rellenarla con valores.
Ahora que tenemos las tablas COFFEES y SUPPLIERS, podremos proceder con el
escenario en que el propietario quería una lista de los cafés comprados a un
suministrador particular. Los nombres de los suminstradores están en la tabla
SUPPLIERS, y los nombres de los cafés en la tabla COFFEES. Como ambas tablas
tienen la columna SUP_ID, podemos utilizar esta columna en una unión. Lo siguiente
que necesitamos es la forma de distinguir la columna SUP_ID a la que nos referimos.
Esto se hace precediendo el nombre de la columna con el nombre de la tabla,
"COFFEES.SUP_ID" para indicar que queremos referirnos a la columna SUP_ID de la
tabla COFFEES. En el siguiente código, donde stmt es un objeto Statement,
seleccionamos los cafés comprados a Acme, Inc..
ResultSet rs = stmt.executeQuery(query);
System.out.println("Coffees bought from Acme, Inc.: ");
while (rs.next()) {
String coffeeName = getString("COF_NAME");
System.out.println(" " + coffeeName);
}
Cuando se crea una conexión, está en modo auto-entrega. Esto significa que cada
sentencia SQL individual es tratada como una transación y será automáticamente
entregada justo después de ser ejecutada. (Para ser más preciso, por defecto, una
sentencia SQL será entregada cuando está completa, no cuando se ejecuta. Una
sentencia está completa cuando todas sus hojas de resultados y cuentas de actualización
han sido recuperadas. Sin embargo, en la mayoría de los casos, una sentencia está
completa, y por lo tanto, entregada, justo después de ser ejecutada).
La forma de permitir que dos o más sentencia sean agrupadas en una transación es
desactivar el modo auto-entrega. Esto se demuestra en el siguiente código, donde con es
una conexión activa.
con.setAutoCommit(false);
con.setAutoCommit(false);
PreparedStatement updateSales = con.prepareStatement(
"UPDATE COFFEES SET SALES = ? WHERE COF_NAME
LIKE ?");
updateSales.setInt(1, 50);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate();
PreparedStatement updateTotal = con.prepareStatement(
"UPDATE COFFEES SET TOTAL = TOTAL + ? WHERE COF_NAME LIKE ?");
updateTotal.setInt(1, 50);
updateTotal.setString(2, "Colombian");
updateTotal.executeUpdate();
con.commit();
con.setAutoCommit(true);
La línea final del ejemplo anterior activa el modo auto-commit, lo que significa que
cada sentencia será de nuevo entregada automáticamente cuando esté completa.
Volvemos por lo tanto al estado por defecto, en el que no tenemos que llamar al método
commit. Es bueno desactivar el modo auto-commit sólo mientras queramos estar en
modo transación. De esta forma, evitamos bloquear la base de datos durante varias
sentencias, lo que incrementa los conflictos con otros usuarios.
Además de agrupar las sentencias para ejecutarlas como una unidad, las transaciones
pueden ayudarnos a preservar la integridad de los datos de una tabla. Por ejemplo,
supongamos que un empleado se ha propuesto introducir los nuevos precios de los cafés
en la tabla COFFEES pero lo retrasa unos días. Mientras tanto, los precios han subido,
y hoy el propietario está introduciendo los nuevos precios. Finalmente el empleado
empieza a intrudir los precios ahora desfasados al mismo tiempo que el propietario
intenta actualizar la tabla. Después de insertar los precios desfasados, el empleado se da
cuenta de que ya no son válidos y llama el método rollback de la Connection para
deshacer sus efectos. (El método rollback aborta la transación y restaura los valores que
había antes de intentar la actualziación. Al mismo tiempo, el propietario está ejecutando
una sentencia SELECT e imprime los nuevos precios. En esta situación, es posible que
el propietario imprima los precios que más tarde serían devueltos a sus valores
anteriores, haciendo que los precio impresos sean incorrectos.
Para evitar conflictos durante una transación, un controlador de base de datos utiliza
bloqueos, mecanismos para bloquear el acceso de otros a los datos que están siendo
accedidos por una transación. (Observa que en el modo auto-commit, donde cada
sentencia es una transación, el bloqueo sólo se mantiene durante una sentencia). Una
vez activado, el bloqueo permanece hasta que la transación sea entregada o anulada. Por
ejemplo, un controlador de base de datos podría bloquear una fila de una tabla hasta que
la actualización se haya entregado. El efecto de este bloqueo es evitar que usuario
obtenga una lectura sucia, esto es, que lea un valor antes de que sea permanente.
(Acceder a un valor actualizado que no haya sido entregado se considera una lectura
sucia porque es posible que el valor sea devuelto a su valor anterior. Si leemos un valor
que luego es devuelto a su valor antiguo, habremos leído un valor nulo).
La forma en que se configuran los bloqueos está determinado por lo que se llama nivel
de aislamiento de transación, que pude variar desde no soportar transaciones en absoluto
a soportar todas las transaciones que fuerzan una reglas de acceso muy estrictas.
Procedimientos Almacenados
Un procedimiento almacenado es un grupo de sentencias SQL que forman una unidad
lógica y que realizan una tarea particular. Los procedimientos almacenados se utilizan
para encapsular un conjunto de operaciones o peticiones para ejecutar en un servidor de
base de datos. Por ejemplo, las operaciones sobre una base de datos de empleados
(salarios, despidos, promociones, bloqueos) podrían ser codificados como
procedimientos almacenados ejecutados por el código de la aplicación. Los
procedimientos almacenados pueden compilarse y ejecutarse con diferentes parámetros
y resultados, y podrían tener cualquier combinación de parámtros de entrada/salida.
> Los procedimientos almacenados están soportados por la mayoría de los controladores
de bases de datos, pero existe una gran cantidad de variaciones en su síntaxis y
capacidades. Por esta razón, sólo mostraremos un ejemplo sencillo de lo que podría ser
un procedimiento almacenado y cómo llamarlos desde JDBC, pero este ejemplo no está
diseñado para ejecutarse.