Está en la página 1de 7

Introducción al JDBC

Este documento ilustra los elementos esenciales del JDBC (Java Database Connectivity) API
(Application Program Interface). Aquí, usted aprenderá a usar el API de JDBC básico para crear las
tablas, insertar valores, consultar tablas, recuperar resultados, actualizar tablas, crear declaraciones
preparadas, realizar transacciones y capturar excepciones y errores.

Este documento se obtiene de la guía didáctica oficial de JDBC, de Sun, JDBC Basics:
http://java.sun.com/docs/books/tutorial/jdbc/basics/index.html
● Resumen General
● Estableciendo una Conexión
● Creando una Instrucción JDBC
● Creando un Objeto PreparedStatement JDBC
● Ejecutando instrucciones CREATE/INSERT/UPDATE
● Ejecutando una Instrucción SELECT
● Notas Sobre Accesos ResultSet
● Transacciones
● Manejando Errores con Excepciones

Resumen general
Las interfaces a nivel de llamado como JDBC son interfaces de programación que permiten el acceso externo a
comandos de manipulación y actualización SQL de la base de datos. Ellos permiten la integración de llamadas del
SQL en un entorno de programación general proporcionando una biblioteca de rutinas las cuales interactúan con la
base de datos. En particular, Java basado en JDBC tiene una rica colección de rutinas que hacen tal interfaz
sumamente simple e intuitiva.

Aquí hay una manera fácil de visualizar lo que pasa en una interfaz de nivel de llamada: Usted está escribiendo un
programa de Java normal. En alguna parte en el programa, usted necesita actuar recíprocamente con una base de
datos. Usando las rutinas de la biblioteca estándar, usted abre una conexión a la base de datos. Usted entonces usa
JDBC entonces para enviar su código del SQL a la base de datos, y procesa los resultados que se devuelven.
Cuando usted ha terminado, cierra la conexión.

Tal enfoque tiene que ser contrastado con la ruta de precompilación tomada con el SQL Embebido. El último tiene
un paso de precompilación dónde el código del SQL embebido se convierte al código del lenguaje huésped
(C/C++). Las interfaces a nivel de llamado no requieren la precompilación y así evitan algunos de los problemas de
SQL embebido. El resultado es aumentar la portabilidad y una relación cliente-servidor más limpia.

Estableciendo una Conexión


La primera cosa para hacer, por su puesto, es instalar Java, JDBC y el DBMS en sus máquinas de trabajo. Desde
que queremos interactuar con una base de datos Oracle, nosotros necesitaríamos también un driver para esta base
de datos específica. Afortunadamente, nosotros tenemos un administrador responsable que ya ha hecho todos esto
para nosotros en las máquinas de la Escuela.
Como dijimos inicialmente, antes de acceder a una base de datos, debe abrirse una conexión entre nuestro
programa (cliente) y la base de datos (servidor). Esto involucra dos pasos:

● Cargue el driver específico


¿Por qué necesitamos este paso? Para asegurar portabilidad y reutilización del código, el API fue diseñado para
ser independiente de la versión o el vendedor de una base de datos. Ya que diferentes DBMS tiene
funcionamiento diferente, necesitamos decirle al driver administrador del DBMS que deseamos usarlo, para que
pueda invocar el driver correcto.

Un driver de Oracle es cargado usando el siguiente trozo de código:


Class.forName("oracle.jdbc.driver.OracleDriver")

● Hacer una conexion


Una vez el driver es cargado y listo para realizar una conexión, usted puede crear una instancia de un objeto
Connection usando:
Connection con = DriverManager.getConnection(
“jdbc:oracle:thin:@192.168.131.93:1521:ictus”, “scott”, ”tigger”);

Veamos lo que significa esta jerga. El primer string es la dirección URL para la base de datos incluyendo el
protocolo (JDBC), el vendedor (Oracle), el driver (thin), el número del puerto (1521), y el servidor
(ictus). El nombre de usuario y la contraseña son scott y tigger, el mismo con que ingresa a SQLPLUS
para acceder su cuenta.

La conexión retornada en el último paso es una conexión abierta que usaremos para pasar las declaraciones SQL a
la base de datos. En este trozo del código, con es una conexión abierta, y nosotros la usaremos abajo.

Creando una Instrucción JDBC


Un objeto Statement JDBC se usa para enviar sus declaraciones del SQL al DBMS, y no debe ser confundida
con una declaración SQL. Un objeto Statement JDBC es asociado con una conexión abierta, y no cualquier sola
Declaración del SQL. Usted puede pensar en un JDBC Declaración objeto como un cauce que se sienta en una
conexión, y pasando uno o más de sus declaraciones del SQL (qué usted le pide que ejecute) al DBMS.

Se necesita una conexión activa para crear un objeto Statement. El siguiente trozo de código, usando nuestro
objeto Connection con, lo hace:
Statement stmt = con.createStatement() ;

En este momento, existe un objeto Statement, pero no tiene una instrucción SQL para pasar al DBMS. Nosotros
aprenderemos como hacerlo en la sección siguiente.

Creando un Objeto PreparedStatement JDBC

Algunas veces, es más conveniente o más eficiente usar un objeto PreparedStatement para enviar las
instrucciones SQL al DBMS. La característica principal que lo distingue de su superclase Statement, es que a
diferencia de la instrucción Statement, ella está dando una instrucción SQL correcta cuando se crea. Esta
instrucción SQL se envía en seguida al DBMS, dónde es compilada. Entonces, en efecto, un objeto
PreparedStatement es asociado como un canal con una conexión y una instrucción SQL compilada.

La ventaja ofrecida es que si necesita usar la misma, o similar consulta con diferentes parámetros varias veces, la
instrucción puede ser compilada y optimizada por el DBMS sólo una vez. Contrasta esto con un uso de una
Statement normal dónde cada uso de la misma instrucción SQL requiere una compilación de nuevo.

También se crean PreparedStatements con un método Connection. El siguiente trozo muestra cómo
crear una instrucción SQL parametrizada con tres parámetros de entrada:
PreparedStatement prepareUpdatePrice = con.prepareStatement(
"UPDATE Venta SET precio = ? WHERE nombreBar = ? AND marcaCerveza = ?");

Antes de que podamos ejecutar un PreparedStatement, necesitamos proporcionar los valores de los
parámetros. Esto puede hacerse llamando uno de los métodos del setXXX definido en la clase
PreparedStatement. Los métodos más usados son los setInt, setFloat, setDouble,
setString, etc. Usted puede poner estos valores antes de cada ejecución de la instrucción preparada.

Continuando el ejemplo anterior, escribiremos:


prepareUpdatePrecio.setInt(1, 1000);
prepareUpdatePrecio.setString(2, "El Cantinazo");
prepareUpdatePrecio.setString(3, "Rivereña");

Ejecutando una Instrucción CREATE/INSERT/UPDATE


La Ejecución de las instrucciones SQL en JDBC varía dependiendo de la “la intención” de la instrucción SQL. Las
instrucciones DDL (data definition language) tales como la creación de una tabla y la alteración de una tabla, así
como las declaraciones para actualizar el contenido de una tabla, son todas ejecutadas usando el método
executeUpdate. Note que estos comandos cambian el estado de la base de datos, ya que el nombre del método
contiene “Update”.

Veamos los siguientes ejemplos de instrucciones executeUpdate:


Statement stmt = con.createStatement();

stmt.executeUpdate("CREATE TABLE Venta " +


"( nombreBar VARCHAR2(40), marcaCerveza VARCHAR2(40), precio REAL)" );
stmt.executeUpdate("INSERT INTO Venta " +
"VALUES ('El Cantinazo', 'Rivereña', 1000)" );

String sqlString = "CREATE TABLE Bares " +


"( nombreBar VARCHAR2(40), direccion VARCHAR2(80), licencia INT)" ;
stmt.executeUpdate(sqlString);

Ya que la instrucción SQL no encajará en una línea en la página, hemos dividido en dos cadenas concatenadas por
un signo más (+) para que compilara. Preste atención a la instrucción "INSERT INTO Venta", se ha separado
en la cadena de la cláusula “VALUES". Note que estamos reusando el mismo objeto Statement en lugar de haber
creado un nuevo objeto.

Cuando se usa executeUpdate para llamar las instrucciones DDL, el valor devuelto es siempre cero, mientras
la ejecución de instrucciones de modificación de datos devolverán un valor mayor que cero, el cual es el número de
tuplas afectadas en la relación.

Mientras esta trabajando con un PreparedStatement, debemos ejecutar tal consulta por el primer plugging en
los valores de los parámetros (como vimos anteriormente), y luego invocando el executeUpdate sobre él.
int n = prepareUpdatePrecio.executeUpdate();
Ejecutando una Instrucción SELECT
En oposición a las instrucciones anteriores, se espera que una consulta devuelva un conjunto de tuplas como
resultado, y no cambia el estado de la base de datos. No es sorprendentemente, ya que un método correspondiente
llamado executeQuery, devuelve sus resultados como un objeto de ResultSet:
String nombreBar, marcaCerveza ;
float precio ;

ResultSet rs = stmt.executeQuery("SELECT * FROM Venta");


while ( rs.next() ) {
nombreBar = rs.getString("nombreBar");
marcaCerveza = rs.getString("marcaCerveza");
precio = rs.getFloat("precio");
System.out.println(nombreBar+" vende "+marcaCerveza+" a "+precio+" Pesos.");
}

La bolsa de tuplas resultante de la consulta está contenida en la variable rs, la cual es una instancia de
ResultSet. Un conjunto no es de mucha utilidad a menos que podemos acceder cada fila y los atributos en cada
fila. El ResultSet nos proporciona un cursor, que podemos usar para acceder cada fila a la vez. El cursor es
inicialmente fijado antes de la primera fila. Cada invocación del método next causa que el cursor se mueva a la
siguiente fila, si ella existe retorna verdadero (true), o retorno falso (false) si no hay fila de retorno.

Podemos usar el método del getXXX del tipo apropiado para recuperar los atributos de una fila. En el ejemplo
anterior, nosotros usamos los métodos getString y getFloat para acceder los valores de la columna. Observe
que debemos proporcionar el nombre de la columna cuyo valor se desea adquirir, como parámetro al método.
También debemos observar que los atributos de tipo VARCHAR2 nombreBar, marcaCerveza, se han convertido a
un String de Java, y el REAL al tipo float de Java.

Equivalentemente, podemos especificar el número de la columna en lugar del nombre de la columna, con el mismo
resultado. Así las declaraciones pertinentes serían:
nombreBar = rs.getString(1);
precio = rs.getFloat(3);
marcaCerveza = rs.getString(2);

Mientras se trabaja con un PreparedStatement, debemos ejecutar tal consulta por el primer plugging en los
valores de los parámetros, e invocando luego el executeQuery sobre él.
ResultSet rs = prepareUpdatePrice.executeQuery() ;

Notas Sobre Accessos ResultSet

JDBC también le ofrece varios métodos para averiguar dónde está ubicado en el conjunto resultante usando
getRow, isFirst, isBeforeFirst, isLast, isAfterLast.

Hay la forma de hacer los cursores desplegables para permitir libre acceso a cualquier fila en el conjunto resultante.
Por defecto, los cursores desplazan hacia adelante solamente y sólo son de lectura. Cuando se crea una
Statement para una Connection, se puede cambiar el tipo de ResultSet a un modelo desplegable más
flexible o actualizable:
Statement stmt = con.createStatement(
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT * FROM Venta");

Las diferentes opciones para los tipos son TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE y


TYPE_SCROLL_SENSITIVE. Usted puede escoger si el cursor es de sólo lectura o actualizable usando las
opciones CONCUR_READ_ONLY, y CONCUR_UPDATABLE. Con el cursor predefinido, usted puede desplazar
usando rs.next(). Con los cursores desplegabless usted tiene más opciones:
rs.absolute(3); // se mueve a la tercera tupla
rs.previous(); // se mueve hacia atrás una tupla (tupla 2)
rs.relative(2); // se mueve hacia delante dos tuplas (tupla 4)
rs.relative(-3); // se mueve hacia atrás tres tuplas (tuple 1)

Hay una gran cantidad de detalles en el desempeño de un cursor desplegable. Los cursores desplegables, aunque
bastante útiles para ciertas aplicaciones, son extremadamente susceptibles de traspasar los límites (overhead), y
deben usarse en forma restringida y con cautela. Más información puede encontrarse en
http://java.sun.com/docs/books/tutorial/jdbc/jdbc2dot0/index.html
dónde puede hallar una guía didáctica más detallada acerca de las técnicas de manipulación del cursor.

Transacciones
JDBC permite agrupar instrucciones SQL en una sola transacción. Así, podemos asegurar las propiedades ACID
(Atomicidad, Consistencia, Aislamiento, Durabilidad) usando las facilidades transaccionales del JDBC.

El control de la transacción es realizado por el objeto Connection. Cuando una conexión se crea, por defecto es
en modo auto - commit. Esto significa que cada instrucción individual SQL se trata como una transacción en sí
misma, y se comprometerá en cuanto la ejecución sea terminada. (Esto no es exactamente preciso, pero podemos
encubrir esta sutileza por propósitos didácticos).

Nosotros podemos desactivar el modo auto - commit para una conexión activa con:
con.setAutoCommit(false) ;

y se lo vuelve a activar con:


con.setAutoCommit(true) ;

Una vez que el auto-commit is desactivado, ninguna declaración del SQL se comprometerá (es decir, la base de
datos no se actualizará permanentemente) hasta que usted le haya dicho explícitamente que comprometa invocando
el método commit():
con.commit() ;

A cualquier punto antes del commit, podemos invocar un rollback () para reversar la transacción, y restaurar
los valores al último punto de commit (antes de las actualizaciones intentadas).
Aquí hay un ejemplo que presenta estas ideas:
con.setAutoCommit(false);
Statement stmt = con.createStatement();
stmt.executeUpdate("INSERT INTO Venta VALUES('El Cantinazo','Rivereña',1000)" );
con.rollback();
stmt.executeUpdate("INSERT INTO Sells VALUES('Bar Ato', 'Mellir', 1200)" );
con.commit();
con.setAutoCommit(true);

Examinemos el ejemplo para entender los efectos de varios de los métodos. Primero se declaró desactivar el auto-
commit, indicando eso que las siguientes instrucciones necesitaban considerarse como una unidad. Luego
intentamos insertar en la tabla Venta la tupla ('El Cantinazo','Rivereña',1000). Sin embargo, este
cambio no será realizado hasta el final (cuando se ejecute el commit). Cuando invocamos el rollback, nosotros
cancelamos la anterior inserción y en efecto se elimina cualquier intención de insertar la tupla anterior. Note que
Venta ahora todavía es como era antes de que intentáramos la inserción. Ahora intentamos otra inserción, y esta
vez, nosotros comprometemos la transacción. Sólo es ahora que Venta se afecta permanentemente y tiene una
nueva tupla en ella. Finalmente, restablecemos la conexión para realizar de nuevo un auto-commit.

También podemos asignar niveles de aislamiento de la transacción como se desee. Por ejemplo, podemos asignar el
nivel de aislamiento de transacción en TRANSACTION_READ_COMMITTED, que no permitirá acceder un valor
hasta después de que haya sido comprometido (committed), y puede prohibir las lecturas sucias. Hay cinco que
valores para niveles de aislamiento proporcionados en la interfaz Connection. Por defecto, el nivel de
aislamiento es el serializable. JDBC nos permite definir el nivel de aislamiento de transacción a la base de datos
(usando el método de Connection : getTransactionIsolation ) y asignar el nivel apropiado (usando el
método de Connection : setTransactionIsolation).

Normalmente se usará rollback en combinación con el manejo de excepciones de Java para recuperar errores
predecibles (o no). Tal una combinación provee un mecanismo excelente y fácil para el manejo de integridad de los
datos. Estudiamos el tratamiento de errores que usa JDBC en la siguiente sección.

Manejo de Errores con Exceptions

La verdad es que siempre ocurren errores en los programas de software. A menudo, los programas de base de datos
son aplicaciones críticas, y es indispensable que los errores se capturen y se manejen limpiamente. Los Programas
deben recuperar y dejar la base de datos en un estado consistente. El rollback usado junto con el manejo de
excepciones de Java es una forma decorosa de lograr tal requisito.

El programa (cliente) accediendo una base de datos (servidor) necesita ser consciente de cualquier error devuelto
por el servidor. JDBC da acceso a la tal información proporcionando dos niveles de condiciones del error:
SQLException y SQLWarning. SQLExceptions son las excepciones de Java que, si no manejó, terminarán
la aplicación. SQLWarnings son subclasses de SQLException, pero ellas representan errores no fatales o
condiciones inesperadas, las cuales puede ignorarse.

En Java, declaraciones de las cuales se espera disparen una excepción o una advertencia son encerradas en un
bloque try. Si una declaración en bloque try dispara una excepción o una advertencia, puede ser capturada en
una de las declaraciones de captura (catch) correspondientes. Cada declaración catch especifica cual
excepciones esta disponible para capturar.

Aquí es un ejemplo de captura de un SQLException, y usando la condición de error para reversar la transacción:
try {
con.setAutoCommit(false) ;
stmt.executeUpdate("CREATE TABLE Venta (nombreBar VARCHAR2(40)," +
" marcaCerveza VARHAR2(40), precio REAL)") ;
stmt.executeUpdate("INSERT INTO Venta VALUES " +
"('Bar Ato', 'Mellir', 1200)") ;
con.commit() ;
con.setAutoCommit(true) ;

}catch(SQLException ex) {
System.err.println("SQLException: " + ex.getMessage()) ;
con.rollback() ;
con.setAutoCommit(true) ;
}

En este caso, se dispara una excepción porque marcaCerveza está definida como VARHAR2 que es un error de
sintaxis. No hay tal tipo de datos en nuestro DBMS, entonces se dispara un SQLException. La salida en este caso
será:
Message: ORA-00902: invalid datatype
Alternativamente, si los tipos de dato están correctos, una excepción podría dispararse en caso de que el tamaño de
la base de datos exceda la cuota de espacio y sea incapaz de construir una nueva tabla. Un SQLWarning puede
ser regresado de los objetos Connection, Statement y ResultSet. Cada uno sólo almacena el más
reciente SQLWarning. Así si usted ejecuta otra declaración a través de su objeto Statement, cualquier
advertencia más temprana será descartada. Aquí hay un trozo de código que ilustra el uso de SQLWarning:
ResultSet rs = stmt.executeQuery("SELECT nombreBar FROM Venta") ;
SQLWarning warn = stmt.getWarnings() ;
if (warn != null)
System.out.println("Message: " + warn.getMessage()) ;
SQLWarning warning = rs.getWarnings() ;
if (warning != null)
warning = warning.getNextWarning() ;
if (warning != null)
System.out.println("Message: " + warn.getMessage()) ;

Los SQLWarnings (en oposición a SQLExceptions) son realmente raros -- el más común es una advertencia
de DataTruncation. Este último indica que hay un problema mientras esta leyendo o escribiendo datos de la
base de datos.