Cuando se desea manipular una base de datos a partir de un lenguaje de programación, se dispone de dos soluciones: Comunicar directamente con la base de datos.
Utilizar una capa software que asegure el diálogo con la base de datos. La primera solución comporta varios requisitos.
Debe dominar perfectamente la programación de red.
También debe conocer en detalle el protocolo utilizado por la base de datos.
Este tipo de desarrollo suele ser muy largo y lleno de trabas. Por ejemplo, ¿puede acceder a las especificaciones del protocolo?
Deberá empezar de nuevo todo su trabajo si cambia de tipo de base de datos ya que por supuesto los protocolos no son compatibles de una base de datos a otra. O incluso peor, de una versión a otra de una misma base de datos.
Por supuesto, la segunda solución es preferible y es ésta la que los diseñadores de Java han elegido. Por lo tanto, desarrollaron la biblioteca jdbc para el acceso a una base de datos. Precisamente, la biblioteca jdbc se compone de dos partes. La primera parte, contenida en el paquete java.sql, se compone esencialmente de interfaces. Estas interfaces son implementadas por los drivers jdbc. Estos drivers (o puentes) no están desarrollados por Sun sino, en general, por la empresa diseñadora de la base de datos. Efectivamente, es este último quien domina mejor la técnica para comunicarse con su base de datos. Existen cuatro tipos de drivers jdbc con características y resultados diferentes.
Tipo 1: Driver jdbc-odbc
Este tipo de puente no es específico de una base de datos concreta sino que traduce simplemente las instrucciones jdbc en instrucciones odbc. A continuación, es el driver odbc quien asegura la comunicación con la base de datos. Esta solución sólo ofrece resultados mediocres porque dependen del número de capas software utilizadas. Las funcionalidades jdbc también son limitadas por las de la capa odbc. Sin embargo, esta solución le permite acceder a casi cualquier base de datos. Se debe plantear esta solución cuando no exista otro driver disponible.
Tipo 2: Driver nativo
Este tipo de puente no está escrito completamente en Java. La parte de este driver escrita en Java efectúa sencillamente llamadas hacia funciones del driver nativo. Se efectúan estas llamadas mediante la API JNI (Java Native Interface). De la misma forma que para los puentes de tipo 1, hace falta una traducción entre el código Java y la base de datos. Sin embargo, este tipo sigue siendo más eficaz que los drivers jdbc-odbc.
Tipo 3: Driver que utiliza un servidor intermediario
Este puente de protocolo de red, desarrollado completamente en Java, no comunica directamente con la base de datos sino que se dirige a un servidor intermediario. El diálogo entre el driver y el servidor intermediario es estándar sea cual sea el tipo de base de datos. Luego, es este servidor intermediario quien transmite las peticiones utilizando un protocolo específico a la base de datos.
Tipo 4: Driver completamente escrito en Java
Este tipo de driver representa la solución ideal ya que no hay intermediario alguno. El driver transmite directamente las peticiones a la base de datos utilizando el protocolo propio de la base de datos. La mayoría de los puentes actuales son de este tipo.
1. Presentación de jdbc
La biblioteca jdbc proporciona un conjunto de clases y sobre todo de interfaces que permiten la manipulación de una base de datos. Estos elementos representan todo lo necesario para acceder a los datos desde una aplicación Java. El esquema siguiente retoma el camino lógico desde el driver hasta los datos.
http://www.eni-training.com/client_net/mediabook.aspx?idR=65894 2/12
La clase DriverManager es nuestro punto de partida. Es ella quien asegura el vínculo con el driver. A través de ella, podemos obtener una conexión hacia la base de datos. Está representada por una instancia de clase que implementa la interfaz Connection. Luego se utiliza esta conexión para transmitir instrucciones a la base de datos. Las peticiones simples son ejecutadas mediante la interfaz Statement, las peticiones con parámetros, con la interfaz PreparedStatement y los procedimientos almacenados con la interfaz CallableStatement. Los eventuales registros seleccionados por la instrucción SQL son recogidos en forma de un elemento Resultset. Vamos a detallar estas diferentes etapas en los párrafos siguientes.
2. Carga del driver
La primera etapa indispensable es obtener el driver jdbc adaptado a su base de datos. En general, este controlador está disponible bajo descarga del sitio del diseñador de la base de datos. Para nuestros ejemplos, utilizaremos el controlador facilitado por Microsoft para acceder a un servidor de base de datos SQL Server 2005. Se le puede descargar en la dirección URL siguiente:http://msdn.microsoft.com/en-us/data/aa937724.aspx
Tras descomprimir el fichero, deberá obtener un directorio que contiene el propio driver bajo forma de un archivo java (sqljdbc.jar) y varios ficheros de ayuda relativos al uso de este controlador. El fichero jar contiene las clases desarrolladas por Microsoft que implementan las diferentes interfaces jdbc. Por supuesto, este fichero deberá estar accesible en el momento de la compilación y de la ejecución de la aplicación.
A continuación, se debe cargar este driver mediante el método forName de la clase Class. Este método espera como parámetro una cadena de caracteres que contiene el nombre del driver. A estas alturas, es indispensable recoger la documentación del driver para obtener el nombre de la clase. En nuestro caso, esta clase lleva el nombre siguiente:
com.microsoft.sqlserver.jdbc.SQLServerDriver
El registro del driver se crea por lo tanto con la instrucción siguiente: Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
En cuanto al driver que permite el acceso a una fuente de datos ODBC está directamente disponible con el JDK. Se puede cargar con la sintaxis siguiente:
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Las cadenas de caracteres pasadas como parámetros representan nombres de clases, por lo tanto son sensibles a mayúsculas y minúsculas. De hecho, se debe proteger la instrucción forName con un bloque try catch ya que es susceptible de activar una excepción de tipoClassNotFoundException.
3. Establecer y manipular la conexión
Tras su descarga, el driver es capaz de facilitarnos una conexión hacia el servidor de base de datos.
a. Establecer la conexión
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConexionDirecta {
public static void main(String[] args) { try { Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); } catch (ClassNotFoundException e) {
System.out.println("error durante la carga del driver"); } Connection cnxDirect=null; try { cnxDirect=DriverManager.getConnection("jdbc:sqlserver: //localhost;databaseName=northwind; user=thierry;password=secret;"); } catch (SQLException e) {
System.out.println("error durante la conexión"); } } } import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConexionDirecta {
public static void main(String[] args) { try { Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch (ClassNotFoundException e) {
System.out.println("error durante la carga del driver"); } Connection cnxOdbc=null; try { cnxOdbc=DriverManager.getConnection("jdbc:odbc:nwd","thierry","secret"); } catch (SQLException e) {
System.out.println("error durante la conexión"); }
} }
Cada una de las versiones de este método espera como parámetro una cadena de caracteres que representa una URL que contiene los datos necesarios para establecer la conexión. El contenido de esta cadena de caracteres es específico para cada driver y hay que consultar de nuevo la documentación para obtener la sintaxis adecuada. Sin embargo, el principio de esta cadena de caracteres es estándar con la forma siguientejdbc:nombreDelProtocolo. El nombre del protocolo es propio a cada driver y es gracias a este nombre que el método getConnection es capaz identificar el driver correcto. El resto de la cadena es específica de cada driver. En general, contiene los datos que permiten identificar el servidor y la base de datos en este servidor hacia la cual la conexión debe ser establecida. Para el driver Microsoft, la sintaxis básica es la siguiente:
jdbc:sqlserver://localhost;databaseName=northwind; user=usuario;password=secreto;
Para el controlador ODBC, la sintaxis es más sencilla ya que basta con especificar el nombre de la fuente ODBC con la que debemos conectar. Por supuesto, esta fuente ODBC debe haber sido creada previamente.
jdbc:odbc:nwd
En caso de éxito, el método getConnection devuelve una instancia de clase que implementa la interfaz Connection. En algunos casos, es preferible emplear la segunda versión del métodogetConnection ya que ésta permite indicar el nombre y la contraseña utilizados para establecer la conexión como parámetros separados de la URL de conexión. Aquí, están dos ejemplos que permiten establecer una conexión hacia una misma base de datos. El primero usa el driver Microsoft.
http://www.eni-training.com/client_net/mediabook.aspx?idR=65894 4/12
public static void testSóloLectura(Connection cnx) { boolean estado; try { estado = cnx.isReadOnly(); cnx.setReadOnly(!estado); if (cnx.isReadOnly() != estado) {
System.out.println("este driver se encarga del modo sólo lectura");
} else {
System.out.println("este driver no se encarga del modo sólo lectura");
} cnx.setReadOnly(estado); } catch (SQLException e) { e.printStackTrace(); } }
public static void visualizaciónWarnings(Connection cnx) { SQLWarning aviso; try { aviso=cnx.getWarnings(); if (aviso==null) {
System.out.println("no hay avisos"); } else { while (aviso!=null) { System.out.println(aviso.getMessage()); System.out.println(aviso.getSQLState()); System.out.println(aviso.getErrorCode()); aviso=aviso.getNextWarning(); } } cnx.clearWarnings(); } catch (SQLException e) { e.printStackTrace(); } }
System.out.println("base actual: " +cnxDirect.getCatalog());
b. Manipular la conexión
Una conexión queda abierta desde el momento en que se crea. Por lo tanto, no hay método que permita abrir una conexión. En cambio, se puede cerrar una conexión llamando al método close. Después del cierre de una conexión, ya no es posible usarla y se la debe recrear para que pueda ser de nuevo utilizable. La función isClosed permite comprobar si una conexión está cerrada. Si esta función devuelve un booleano igual a false, se puede pensar con razón que la conexión está abierta y que permite dialogar por lo tanto con la base de datos. En realidad, esto no es siempre así. La conexión puede estar en un estado intermedio: no está cerrada, pero no puede ser utilizada para transferir instrucciones hacia el servidor. Para comprobar la disponibilidad de la conexión, puede utilizar el método isValid. Este método prueba realmente la disponibilidad de la conexión al intentar enviar una instrucción SQL y al verificar que obtiene una respuesta de parte del servidor.
Este método no está implementado en todos los drivers, y si no lo está, su llamada activa una excepción del tipo java.lang.UnsupportedOperationException o un error del tipojava.lang.AbstractMethodError.
Si únicamente se efectúan operaciones de lectura en la base de datos, podemos optimizar la comunicación precisando que la conexión está en sólo lectura. El método setReadOnly permite modificar esta configuración de la conexión. Luego, se puede probar el estado usando el métodoisReadOnly. Para algunos drivers, esta funcionalidad no está implementada y el métodosetReadOnly no surte ningún efecto. La función siguiente verifica si esta funcionalidad está disponible para la conexión que recibe como parámetro.
Durante la ejecución de una instrucción SQL, el servidor puede detectar problemas, y por lo tanto, generar avisos. Estos avisos pueden ser recuperados por el método getWarnings de la conexión. Este método devuelve un objeto SQLWarning representativo del problema encontrado por el servidor. Si el servidor encuentra varios problemas, genera varios objetos SQLWarningencadenados
entre sí. El
método getNextWarning permite obtener el elemento siguiente o null si la lista está terminada. Se puede vaciar la lista con el método clearWarnings. La función siguiente visualiza todos los avisos recibidos por la conexión.
En general, en el momento de la creación de la URL de conexión, uno de sus parámetros determina el nombre de la base hacia la cual queremos establecer una conexión. En el supuesto de que queramos utilizar una fuente de datos ODBC, es el nombre de esta fuente de datos el que se debe indicar, debiendo precisarse el nombre de la base de datos en el momento de la creación de la fuente de datos. En este caso, el nombre de la base de datos puede ser obtenido por el métodogetCatalog. La modificación del nombre de la base de datos, a la que estamos conectados, se lleva a cabo con el método setCatalog al cual hay que proporcionar el nombre de otra base presente en el mismo servidor. Por supuesto, hace falta que la cuenta con la que se abrió la sesión disponga de los derechos de acceso suficientes para esta base de datos. Cabe señalar que con este método, cambiamos de base de datos pero la conexión se sigue refiriendo al mismo servidor. No hay ningún medio para cambiar de servidor sin crear una nueva conexión.
El código siguiente:
System.out.println("cambio de base de datos"); cnxDirect.setCatalog("garaje");
System.out.println("base actual: " +cnxDirect.getCatalog());
public static void infosBase(Connection cn) { ResultSet rs; DatabaseMetaData dbmd; try { dbmd=cn.getMetaData(); System.out.println("tipo de base: " + dbmd.getDatabaseProductName()); System.out.println("versión: " + dbmd.getDatabaseProductVersion());
System.out.println("nombre del driver: " + dbmd.getDriverName());
System.out.println("versión del driver: " + dbmd.getDriverVersion());
System.out.println("nombre del usuario: " + dbmd.getUserName());
System.out.println("url de conexión: " + dbmd.getURL()); rs=dbmd.getTablas(null,null,"%",null); System.out.println("estructura de la base"); System.out.println("base\tesquema\tnombre tabla\ ttipo tabla"); while(rs.next()) {
for (int i = 1; i <=4 ; i++) { System.out.print(rs.getString(i)+"\t"); } System.out.println(); } rs.close(); rs=dbmd.getProcedures(null,null,"%");
System.out.println("los procedimientos almacenados"); System.out.println("base\tesquema\tnombre
procedimiento");
while(rs.next()) {
for (int i = 1; i <=3 ; i++) { System.out.print(rs.getString(i)+"\t"); } System.out.println(); } rs.close(); } catch (SQLException e) { e.printStackTrace(); } }
base actual: northwind cambio de base de datos base actual: garaje
La estructura de la base de datos puede ser obtenida también con el método getMetaData. Este método devuelve un objeto DataBaseMetaData que facilita numerosa información relativa a la estructura de la base de datos. La función siguiente muestra un análisis rápido de esta información.
Todos estos métodos pueden ser de ayuda algún día, pero la meta principal de una conexión es permitir la ejecución de instrucciones SQL. Por lo tanto, es ella la que facilitará los objetos necesarios para la ejecución de estas instrucciones. Se pueden ejecutar tres tipos de instrucciones SQL:
Las peticiones simples Las peticiones precompiladas Los procedimientos almacenados.
A cada uno de estos tipos corresponde un objeto JDBC:
Statement para las peticiones simples
PreparedStatement para las peticiones precompiladas
CallableStatement para los procedimientos almacenados.
Y para terminar, cada uno de estos objetos puede ser obtenido por un método diferente de la conexión:
createStatement para los objetos Statement
prepareStatement para los objetos PreparedStatement prepareCall para los objetos CallableStatement.
http://www.eni-training.com/client_net/mediabook.aspx?idR=65894 6/12
public static void testEjecuta(Connection cnx) { Statement stm; BufferedReader br; String petición; ResultSet rs; boolean resultado; try { stm=cnx.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_ READ_ONLY);
br=new BufferedReader(new InputStreamReader(System.in)); System.out.println("introducir su instrucción SQL:"); petición=br.readLine();
resultado=stm.ejecuta(petición); if (resultado)
{
System.out.println("su instrucción generó un juego de registros"); rs=stm.getResultSet(); rs.last(); System.out.println("contiene " + rs.getRow() + " registros"); } else {
System.out.println("su instrucción modificó registros en la base");
System.out.println("número de registros modificados:" En el párrafo siguiente vemos en detalle el uso de estos métodos.
4. Ejecución de instrucciones SQL
Antes de la ejecución de una instrucción SQL, debemos elegir el tipo de objeto JDBC más apropiado. Las secciones siguientes describen los tres tipos de objetos disponibles y su utilización.
a. Ejecución de instrucciones básicas con el objeto Statement
Este objeto se obtiene con el método createStatement de la conexión. JDBC cuenta con dos versiones de este método; la primera no espera ningún parámetro. En este caso, si se utiliza el objeto Statement para ejecutar una instrucción SQL que genera un juego de registros (select), éste estará en sólo lectura, y con un desplazamiento de los datos hacia delante.
Los datos presentes en este juego de registros no podrán ser modificados y sólo se podrá hacer el recorrido del juego de registros desde el primero hacia el último registro. La segunda versión permite elegir las características del juego de registros generado. Acepta dos parámetros. El primero determina el tipo del juego de registros. Se definen las constantes siguientes:
ResultSet.TYPE_FORWARD_ONLY: el juego de registros será de desplazamiento hacía delante solamente.
ResultSet.TYPE_SCROLL_INSENSITIVE: el juego de registros podrá ser recorrido en los dos sentidos, pero será insensible a los cambios realizados en la base de datos por otros usuarios.
ResultSet.TYPE_SCROLL_SENSITIVE: el juego de registros podrá ser recorrido en los dos sentidos y será sensible a los cambios realizados en la base de datos por otros usuarios.
El segundo determina las posibilidades de modificación de los datos contenidos en el juego de los registros. Las dos constantes siguientes están definidas:
ResultSet.CONCUR_READ_ONLY: los registros son de sólo lectura.
ResultSet.CONCUR_UPDATABLE: los registros pueden ser modificados en el juego de registros.
Este objeto es el más elemental que permite la ejecución de instrucciones SQL. Puede encargarse de la ejecución de cualquier instrucción SQL. Por lo tanto, usted puede ejecutar tanto instrucciones DDL (Data Definition Language) como instrucciones DML (Data Manipulation Language). Sólo hace falta elegir en este objeto el método más adaptado para la ejecución del código SQL. Se dicta la elección de este método por el tipo de resultado que debe facilitar la instrucción SQL. Hay cuatro métodos disponibles:
public boolean execute(String sql): este método permite la ejecución de cualquier instrucción SQL. El booleano devuelto por este método indica si un juego de registros ha sido generado (true) o si simplemente, la instrucción modificó registros en la base de datos (false). Si se genera un juego de registros, puede ser obtenido por el métodogetResultSet. Si ha habido registros modificados en la base de datos, el métodogetUpdateCount devuelve el número de registros modificados. La función siguiente ilustra el uso de estos métodos.
+ stm.getUpdateCount()); }
}
catch (SQLException e) {
System.out.println("su instrucción no funcionó correctamente"); } catch (IOException e) { e.printStackTrace(); } }
public static void testExecuteBatch(Connection cnx) { Statement stm; BufferedReader br; String petición=""; int[] resultados; try { stm=cnx.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
br=new BufferedReader(new InputStreamReader(System.in)); System.out.println("introducir sus instrucciones SQL luego run para ejecutar el lote:");
petición=br.readLine(); while (!peticion.equalsIgnoreCase("run")) { stm.addBatch(petición); requete=br.readLine(); }
System.out.println("ejecución del lote de instrucciones");
resultados=stm.executeBatch(); for (int i=0; i<resultados.length;i++) { switch (resultados[i]) { case Statement.EXECUTE_FAILED: System.out.println("la ejecución de la instrucción " + i + " falló"); break; case Statement.SUCCESS_NO_INFO: System.out.println("la ejecución de la instrucción " + i + " funcionó");
public Resultset executeQuery(String petición): este método fue diseñado especialmente para la ejecución de instrucciones select. El juego de los registros está disponible directamente como valor devuelto por la función.
public int executeUpdate(String petición): este método se adapta perfectamente a la ejecución de instrucciones que modifican el contenido de la base de datos como las instrucciones insert, update, delete. El entero devuelto por esta función indica el número de registros afectados por la modificación.
public int[] executeBatch(): este método permite ejecutar un conjunto de instrucciones SQL por lote. El lote de instrucciones a ejecutar debe ser preparado previamente con los métodos addBatch y clearBatch. El primero recibe como parámetro