Acceso A Bases de Datos JDBC

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 8

Acceso a Bases de Datos [JDBC]

Autor: Sun
Traductor: Juan Antonio Palos (Ozito)

• Utilizar Sentencias Preparadas


o Cuándo utilizar un Objeto PreparedStatement
o Crear un Objeto PreparedStatement
o Suministrar Valores para los Parámetros de un PreparedStatement
o Utilizar una Bucle para asignar Valores
o Valores de retorno del método executeUpdate

Utilizar Sentencias Preparadas


Algunas veces es más conveniente o eficiente utilizar objetos PreparedStatement para
enviar sentencias SQL a la base de datos. Este tipo especial de sentencias se deriva de
una clase más general, Statement, que ya conocemos.

Cuándo utilizar un Objeto PreparedStatement

Si queremos ejecutar muchas veces un objeto Statement, reduciremos el tiempo de


ejecución si utilizamos un objeto PreparedStatement, en su lugar.

La caracterísitca principal de un objeto PreparedStatement es que, al contrario que un


objeto Statement, se le entrega una sentencia SQL cuando se crea. La ventaja de esto es
que en la mayoría de los casos, esta sentencia SQL se enviará al controlador de la base
de datos inmediatamente, donde será compilado. Como resultado, el objeto
PreparedStatement no sólo contiene una sentencia SQL, sino una sentencia SQL que
ha sido precompilada. Esto significa que cuando se ejecuta la PreparedStatement, el
controlador de base de datos puede ejecutarla sin tener que compilarla primero.

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.

Crear un Objeto PreparedStatement

Al igual que los objetos Statement, creamos un objeto PreparedStatement con un


objeto Connection. Utilizando nuestra conexión con abierta en ejemplos anteriores,
podríamos escribir lo siguiente para crear un objeto PreparedStatement que tome dos
parámetros de entrada.

PreparedStatement updateSales = con.prepareStatement(


"UPDATE COFFEES SET SALES = ? WHERE COF_NAME
LIKE ?");
La variable updateSales contiene la sentencia SQL, "UPDATE COFFEES SET
SALES = ? WHERE COF_NAME LIKE ?", que también ha sido, en la mayoría de
los casos, enviada al controlador de la base de datos, y ha sido precompilado.

Suministrar Valores para los Parámetros de un PreparedStatement

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.

Utilizando el objeto updateSales del ejemplo anterior, la siguiente línea de código


selecciona la primera marca de interrogación para un int de Java, con un valor de 75.

updateSales.setInt(1, 75);

Cómo podríamos asumir a partir de este ejemplo, el primer argumento de un método


setXXX indica la marca de interrogación que queremos seleccionar, y el segundo
argumento el valor que queremos ponerle. El siguiente ejemplo selecciona la segunda
marca de interrogación con el string "Colombian".

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.

String updateString = "UPDATE COFFEES SET SALES = 75 " +


"WHERE COF_NAME LIKE 'Colombian'";
stmt.executeUpdate(updateString);

Código 2.

PreparedStatement updateSales = con.prepareStatement(


"UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? ");
updateSales.setInt(1, 75);
updateSales.setString(2, "Colombian");
updateSales.executeUpdate().

Utilizamos el método executeUpdate para ejecutar ambas sentencias stmt


updateSales. Observa, sin embargo, que no se suministran argumentos a
executeUpdate cuando se utiliza para ejecutar updateSales. Esto es cierto porque
updateSales ya contiene la sentencia SQL a ejecutar.

Mirando esto ejemplos podríamos preguntarnos por qué utilizar un objeto


PreparedStatement con parámetros en vez de una simple sentencia, ya que la sentencia
simple implica menos pasos. Si actualizáramos la columna SALES sólo una o dos
veces, no sería necesario utilizar una sentencia SQL con parámetros. Si por otro lado,
tuvieramos que actualizarla frecuentemente, podría ser más fácil utilizar un objeto
PreparedStatement, especialmente en situaciones cuando la utilizamos con un bucle
while para seleccionar un parámetro a una sucesión de valores. Veremos este ejemplo
más adelante en esta sección.

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")

Utilizar una Bucle para asignar Valores

Normalmente se codifica más sencillo utilizando un bucle for o while para asignar
valores de los parámetros de entrada.

El siguiente fragmento de código demuestra la utilización de un bucle for para asignar


los parámetros en un objeto PreparedStatement: updateSales. El array salesForWeek
contiene las cantidades vendidas semanalmente. Estas cantidades corresponden con los
nombres de los cafés listados en el array coffees, por eso la primera cantidad de
salesForWeek (175) se aplica al primer nombre de café de coffees ("Colombian"), la
segunda cantidad de salesForWeek (150) se aplica al segundo nombre de café en
coffees ("French_Roast"), etc. Este fragmento de código demuestra la actualización de
la columna SALES para todos los cafés de la tabla COFFEES

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).

Valores de retorno del método executeUpdate

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

La tabla COFFEES se ha actualziado poniendo el valor 50 en la columna SALES de la


fila correspondiente a Espresso. La actualización afecta sólo a una línea de la tabla, por
eso n es igual a 1.

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.

El sigueinte código crea la tabla SUPPLIERS.

String createSUPPLIERS = "create table SUPPLIERS " +


"(SUP_ID INTEGER, SUP_NAME VARCHAR(40), " +
"STREET VARCHAR(40), CITY VARCHAR(20), " +
"STATE CHAR(2), ZIP CHAR(5))";
stmt.executeUpdate(createSUPPLIERS);
El siguiente código inserta filas para tres suministradores dentro de SUPPLIERS.

stmt.executeUpdate("insert into SUPPLIERS values (101, " +


"'Acme, Inc.', '99 Market Street', 'Groundsville', " +
"'CA', '95199'");
stmt.executeUpdate("Insert into SUPPLIERS values (49," +
"'Superior Coffee', '1 Party Place', 'Mendocino', 'CA',
" + "'95460'");
stmt.executeUpdate("Insert into SUPPLIERS values (150, " +
"'The High Ground', '100 Coffee Lane', 'Meadows', 'CA',
" + "'93966'");

El siguiente código selecciona la tabla y nos permite verla.

ResultSet rs = stmt.executeQuery("select * from SUPPLIERS");

El resultado sería algo similar a esto.

SUP_ID SUP_NAME STREET CITY STATE ZIP


---------- ---------------------- --------------------- ---------------- --------- ---------
101 Acme, Inc. 99 Market Street Groundsville CA 95199
49 Superior Coffee 1 Party Place Mendocino CA 95460
150 The High Ground 100 Coffee Lane Meadows CA 93966

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..

String query = "


SELECT COFFEES.COF_NAME " +
"FROM COFFEES, SUPPLIERS " +
"WHERE SUPPLIERS.SUP_NAME LIKE 'Acme, Inc.'" +
"and SUPPLIERS.SUP_ID = COFFEES.SUP_ID";

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);
}

Esto producirá la siguiente salida.

Coffees bought from Acme, Inc..


Colombian
Colombian_Decaf
Utilizar Transaciones
Hay veces que no queremos que una sentencia tenga efecto a menos que otra también
suceda. Por ejemplo, cuando el propietario del "The Coffee Break" actualiza la cantidad
de café vendida semanalmente, también querrá actualizar la cantidad total vendida hasta
la fecha. Sin embargo, el no querrá actualizar una sin actualizar la otra; de otro modo,
los datos serían inconsistentes. La forma para asegurarnos que ocurren las dos acciones
o que no ocurre ninguna es utilizar una transación. Una transación es un conjunto de una
o más sentencias que se ejecutan como una unidad, por eso o se ejecutan todas o no se
ejecuta ninguna.

Desactivar el modo Auto-entrega

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);

Entregar una Transación

Una vez que se ha desactivado la auto-entrega, no se entregará ninguna sentencia SQL


hasta que llamemos explícitamente al método commit. Todas las sentencias ejecutadas
después de la anterior llamada al método commit serán incluidas en la transación actual
y serán entregadas juntas como una unidad. El siguiente código, en el que con es una
conexión activa, ilustra una transación.

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);

En este ejemplo, el modo auto-entrega se desactiva para la conexión con, lo que


significa que las dos sentencias prepared updateSales y updateTotal serán entregadas
juntas cuando se llame al método commit. Siempre que se llame al método commit
(bien automáticamente, cuando está activado el modo auto-commit o explícitamente
cuando está desactivado), todos los cambios resultantes de las sentencias de la
transación serán permanentes. En este caso, significa que las columnas SALES y
TOTAL para el café Colombian han sido cambiadas a 50 (si TOTAL ha sido 0
anteriormente) y mantendrá este valor hasta que se cambie con otra sentencia de
actualización.

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.

Utilizar Transaciones para Preservar al Integridad de los Datos

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.

Esta clase de situaciones puede evitarse utilizando Transaciones. Si un controlador de


base de datos soporta transaciones, y casi todos lo hacen, proporcionará algún nivel de
protección contra conflictos que pueden surgir cuando dos usuarios acceden a los datos
a la misma vez.

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.

Un ejemplo de nivel de aislamiento de transación es


TRANSACTION_READ_COMMITTED, que no permite que se acceda a un valor
hasta que haya sido entregado. En otras palabras, si nivel de aislamiento de transación
se selecciona a TRANSACTION_READ_COMMITTED, el controlador de la base de
datos no permitirá que ocurran lecturas sucias. El interface Connection incluye cinco
valores que representan los niveles de aislamiento de transación que se pueden utilizar
en JDBC.

Normalmente, no se necesita cambiar el nivel de aislamiento de transación; podemos


utilizar el valor por defecto de nuestro controlador. JDBC permite averiguar el nivel de
aislamiento de transación de nuestro controlador de la base de datos (utilizando el
método getTransactionIsolation de Connection) y permite configurarlo a otro nivel
(utilizando el método setTransactionIsolation de Connection). Sin embargo, ten en
cuenta, que aunque JDBC permite seleccionar un nivel de aislamiento, hacer esto no
tendrá ningún efecto a no ser que el driver del controlador de la base de datos lo soporte.

Cuándo llamar al método rollback

Como se mencionó anteriormente, llamar al método rollback aborta la transación y


devuelve cualquier valor que fuera modificado a sus valores anteriores. Si estamos
intentando ejecutar una o más sentencias en una transación y obtenemos una
SQLException, deberíamos llamar al método rollback para abortar la transación y
empezarla de nuevo. Esta es la única forma para asegurarnos de cuál ha sido entregada y
cuál no ha sido entregada. Capturar una SQLException nos dice que hay algo erróneo,
pero no nos dice si fue o no fue entregada. Como no podemos contar con el hecho de
que nada fue entregado, llamar al método rollback es la única forma de asegurarnos.

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.

También podría gustarte