JDBC

JDBC

JDBC es un conjunto de clases e interfaces Java para la ejecución de sentencias SQL

Es la parte CLI de Java (Call-Level Interface)

Fue desarrollado conjuntamente por JavaSoft, Sybase, Informix e IBM entre otros

JDBC permite la manipulación de cualquier base de datos SQL

No es necesario hacer un programa específico para manipular Oracle, Sybase, etc…

Nuestro programa puede manipular cualquier base de datos

Uniendo Java con JDBC obtenemos programas que se pueden ejecutar en cualquier plataforma y que pueden manipular cualquier base de datos

Las clases e interfaces JDBC se encuentran dentro del paquete java.sql

Proceso de trabajo con JDBC

El proceso de trabajo en JDBC consiste en los siguientes pasos:

  1. Conectarse a la base de datos (mediante DriverManager)

  2. Emitir sentencias SQL (mediante Statement, PreparedStatement, CallableStatement)

  3. Procesamiento de los resultados (ResultSet)

Driver JDBC

Cada base de datos concreta implementa la interface JDBC de una manera particular, al igual que las clases auxiliares y de utilidad que sean necesarias para esa base de datos

Por eso son necesarios los drivers de bases de datos

Una misma aplicación podrá conectar con distintas bases de datos simplemente cambiando ese driver (no debemos confundir el driver de la base de datos con la clase Driver, pues son cosas distintas)

El interface Driver especifica los métodos que todo driver JDBC debe implementar, para ello se pueden cargar los drivers de dos maneras:

  1. Cuando se inicia la clase Driver, el DriverManager consulta la propiedad jdbc.drivers

    Dicha propiedad contiene una lista de drivers (las clases) que deben ser cargadas

    Para ello deberá ejecutarse un comando como este (para usar ODBC) en la línea de comandos:

  2. Si se desea introducir un nuevo driver después de que el DriverManager se halla inicializado se deberá usar el método forname de la clase Class

    Para ello deberemos incluir en nuestro código la siguiente instrucción (para usar ODBC):

    Es aconsejable que se use de forma static en nuestra aplicación (porque sólo se cargará el driver una vez, al cargar la clase)

Ejemplos de drivers
Base de datos Driver
ODBC sun.jdbc.odbc.JdbcOdbcDriver
Oracle oracle.jdbc.driver.OracleDriver
SQLServer com.microsoft.jdbc.sqlserver.SQLServerDriver
MySQL com.mysql.jdbcDriver

Tipos de driver:

  • Puente JDBC-ODBC

    Traduce JDBC a ODBC y lo retransmite al driver ODBC de la máquina

    Es el driver ODBC el que realmente se comunica con la base de datos

    Está incluido en el JDK pero no incluye JDBC2

    Inconvenientes:

    • Es útil para realizar pruebas, pero es lento en producción

    • Es necesario el driver ODBC en el cliente (menor portabilidad)

  • Driver JDBC sobre driver nativo de la BD

    Retransmite JDBC al driver nativo instalado en la máquina

    El driver nativo es el que realmente se comunica con al base de datos

    Inconveniente:

    • necesidad de driver nativo (menor portabilidad)
  • Driver Java sobre red

    Traduce las llamadas JDBC a un protocolo de red independiente de la plataforma que contacta con el servidor

    El servidor traduce esas peticiones al protocolo concreto de cada base de datos

    Se usa un middleware en el servidor de red que es capaz de conectar a los clientes puros Java a muchas bases de datos diferentes

    Ventaja:

    • es rápido, independiente de la plataforma y no requiere de instalación en el cliente

  • Driver puro Java con protocolo nativo

    Traduce las llamadas JDBC al protocolo específico de la base de datos contactando directamente con ella

Obtener conexiones

Mediante el driver concreto realizaremos las conexiones, pero las solicitudes de conexión deben ser realizadas mediante el DriverManager

Una vez que la clase Driver ha sido cargada y registrada, el DriverManager puede establecer conexiones con la base de datos mediante estos dos pasos:

  1. Se llama al método DriverManager.getConnection(url, user, pass); y se obtiene un objeto de tipo Connection

  2. El DriverManager prueba los drivers registrados para ver si puede establecer la conexión y si no fue posible, lanza una SQLException

Una misma aplicación, puede tener varias conexiones a la misma base de datos o varias conexiones a otras bases de datos (hasta el máximo permitido por la base de datos)

Los parámetros que admite getConnection son la url (que es un subprotocolo que utiliza la base de datos para realizar la conexión), el user (el nombre de usuario que va a conectarse) y el pass (la contraseña que utiliza el usuario para conectarse)

El parametro url tiene el siguiente formato:

El subprotocolo es particular de cada base de datos y lo utiliza el DriverManager para buscar el driver adecuado para manipularla

El subnombre depende del subprotocolo concreto

El driver será el encargado de interpretarlo y le ayudará a localizar la base de datos

Nuestra aplicación no trabajará con el driver concreto, sino que lo hará el DriverManager

De esta manera las aplicaciones pueden trabajar con el objeto Connection sin preocuparse por el tipo de base de datos con la cual estemos trabajando (no hay que hacer modificaciones en el código, solo cambiar el parámetro url)

La conexión debe cerrarse siempre al finalizar, porque sino se consumen recursos de forma innecesaria, aunque se pueden reutilizar conexiones, no es aconsejable, es mejor usar siempre una conexión nueva

En el ejemplo se ha utilizado un esqueleto de conexión para Oracle

Para utilizar la conexión debemos crear un objeto de tipo Connection, el cual representa una sesión abierta con la base de datos

El objeto provee de un contexto con el que poder emitir sentencias SQL y obtener resultados

El objeto Connection debe inicializarse inicialmente a null, así podremos comprobar si hubo conexión porque incluiremos un bloque try para gestionar errores, el cuál lanzará SQLException

Para cerrar la conexión debe hacerse mediante el método close(), dentro de un finaly (el cual también tendrá su bloque try que lanza SQLException)

El interface Statement

La conexión nos permite crear objetos Statement mediante el método createStatemen()

Los objetos Statement permiten la ejecución de sentencias SQL y la obtención de resultados (mediante los objetos ResultSet)

Los métodos para ejecutar SQL aceptan String como parámetro (JDBC es CLI), los cuales pueden componerse dinámicamente en función de valores contenidos en variables

Para componer el String SQL habrá que concatenar los distintos fragmentos SQL (los estáticos y las variables)

Hay tres formas de ejecutar sentencias SQL:

  1. Mediante el método executeQuery(<sql>) para consultas mediante la sentencia SELECT que produce túplas como resultado (devuelve un objeto de tipo ResultSet)

    • Es como una tabla que almacena el resultado de la consulta

    • Tiene una serie de métodos de consulta

    • Es como un cursor (en terminología PL / SQL)

  2. Mediante el método executeUpdate(<sql>) para actualizaciones mediante las sentencias INSERT, DELETE, UPDATE, así como comandos DDL (CREATE, DROP, ALTER TABLE, ADD) y bloques PL / SQL (entre los bloques begin y end)

    • Devuelve un entero que indica el número de filas modificadas por el comando

    • Con los comandos DDL devuelve 0

  3. Mediante el método execute(<sql>) que ejecuta cualquier sentencia SQL

    • Si se utilizó un QUERY, devolverá True

      • El ResultSet se puede obtener mediante el método getResultSet()

    • Si se utilizó un UPDATE, devolverá False

      • El total de filas modificadas se pueden obtener mediante el método getUpdateCount()

Después de procesar el resultado, se debe cerrar el Statement mediante el método close()

Este método cierra también el ResultSet asociado, de todas formas, Sun recomendaba cerrar el ResultSet de forma explícita para evitar errores no deseados

Un mismo Statement puede reutilizarse en una misma conexión para ejecutar distintas sentencias

El interface ResultSet

El objeto ResultSet actúa como un cursor dentro de los resultados

La primera vez que se lee apunta encima del primer resultado, pero no lo lee, por eso hay que leer la primera fila avanzando mediante el método next()

El método next() devuelve True si pudo avanzar o False si no pudo

Para obtener los valores del ResultSet se utilizan los métodos get que tienen el siguiente formato get<tipo>(<columna>)

Para nombrar las columnas podemos o hacerlo por su nombre o por un indice, el cual empieza en 1, su numeración viene dada por el orden en que fue introducida en el SELECT

Hay que prestar atención a las fechas, porque se guardan como java.sql.Date, no como java.util.Date como se podría esperar

Conviene cerrar el ResultSet aunque se cierra implícitamente al cerrar o reutilizar el Statement que lo creó

Cuando se ha leído un null de SQL usando uno de los métodos get<tipo>, éste devuelve:

  • Un valor null de Java para aquellos métodos que devuelven objetos Java (getString(), getBigDecimal(), getDate(), getTime(), getTimestamp(), getObject(), etc)

  • Un valor 0 para aquellos métodos que devuelven tipos numéricos (getByte(), getShort(), getInt(), getLong(), getFloat(), getDouble() )

  • Un valor False para el método getBoolean()

Para determinar si un valor dado era null, primero debe intentar leerse la columna y usar el método de ResulSet wasNull() para saber si fue null

Devolverá True si lo era, False en caso contrario

El interface PreparedStatement

Cada vez que se lanza un Statement la base de datos debe interpretarla y calcular un plan de consulta

Pero al usar PreparedStatement, se puede ejecutar múltiples veces, obteniendo un aumento de rendimiento al tener la consulta ya analizada y optimizada

No todas las bases de datos soportan PreparedStatement, hay que leer la documentación de la misma para saber si se pueden usar

Los objetos PreparedStatement derivan del Statement obtenido de la conexión, mediante el método prepareStatement(<sql>);

Un PreparedStatement sirve para lanzar instrucciones SQL precompiladas, podremos parametrizar una o más entradas mediante el operador ?, cuyos valores podrán ser modificados en distintas ejecuciones de la instrucción

Parametrizar las entradas es útil cuando desconocemos los valores de ciertos tipos de datos SQL en la base de datos de destino,

El valor parametrizado obtiene el valor correcto del driver de la base de datos, por lo que no deberemos preocuparnos por los tipos

Antes de que la instrucción SQL pueda ser ejecutada deberemos asignarle un valor a los parámetros de entrada

Para asignar valores usaremos los métodos set<tipo>(columna, valor); siendo el tipo compatible con el del parámetro

Para ejecutarlo se usan los métodos execute de Statement

En el ejemplo se ha utilizado un PreparedStatement para Oracle