|
Aproximación a JDBC |
Anterior | Siguiente |
JDBC define ocho interfaces para operaciones con bases de datos, de las que se derivan las clases correspondientes. La figura siguiente, en formato OMT, con nomenclatura UML, muestra la interrelación entre estas clases según el modelo de objetos de la especificación de JDBC. La clase que se encarga de cargar inicialmente todos los drivers JDBC disponibles es DriverManager. Una aplicación puede utilizar DriverManager para obtener un objeto de tipo conexión, Connection, con una base de datos. La conexión se especifica siguiendo una sintaxis basada en la especificación más amplia de los URL, de la forma jdbc:subprotocolo//servidor:puerto/base de datos Por ejemplo, si se utiliza mSQL el nombre del subprotocolo será msql. En algunas ocasiones es necesario identificar aún más el protocolo. Por ejemplo, si se usa el puente JDBC-ODBC no es suficiente con jdbc:odbc, ya que pueden existir múltiples drivers ODBC, y en este caso, hay que especificar aún más, mediante jdbc:odbc:fuente de datos. Una vez que se tiene un objeto de tipo Connection, se pueden crear sentencias, statements, ejecutables. Cada una de estas sentencias puede devolver cero o más resultados, que se devuelven como objetos de tipo ResultSet. Y la tabla siguiente muestra la misma lista de clases e interfaces junto con una breve descripción.
La primera aplicación que se va a crear simplemente crea una tabla en el servidor, utilizando para ello el puente JDBC-ODBC, siendo la fuente de datos un servidor SQL Server. Si el lector desea utilizar otra fuente ODBC, no tiene más que cambiar los parámetros de getConnection() en el código fuente. El establecimiento de la conexión es, como se puede es fácil suponer, la parte que mayores problemas puede dar en una aplicación de este tipo. Si algo no funciona, cosa más que probable en los primeros intentos, es muy recomendable activar la traza de llamadas ODBC desde el panel de control. De esta forma se puede ver lo que está haciendo exactamente el driver JDBC y por qué motivo no se está estableciendo la conexión. El siguiente diagrama relaciona las cuatro clases principales que va a usar cualquier programa Java con JDBC, y representa el esqueleto de cualquiera de los programas que se desarrollan para atacar a bases de datos. La aplicación siguiente es un ejemplo en donde se aplica el esquema anterior, se trata de instalación java2101.java, crea una tabla y rellena algunos datos iniciales. import java.sql.*; class java2101 { static public void main( String[] args ) { Connection conexion; Statement sentencia; ResultSet resultado; System.out.println( "Iniciando programa." ); // Se carga el driver JDBC-ODBC try { Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" ); } catch( Exception e ) { System.out.println( "No se pudo cargar el puente JDBC-ODBC." ); return; } try { // Se establece la conexión con la base de datos conexion = DriverManager.getConnection( "jdbc:odbc:Tutorial","","" ); sentencia = conexion.createStatement(); try { // Se elimina la tabla en caso de que ya existiese sentencia.execute( "DROP TABLE AGENDA" ); } catch( SQLException e ) {}; // Esto es código SQL sentencia.execute( "CREATE TABLE AMIGOS ("+ " NOMBRE VARCHAR(15) NOT NULL, " + " APELLIDOS VARCHAR(30) NOT NULL, " + " CUMPLE DATETIME) " ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('JOSE','GONZALEZ','03/15/1973')" ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('PEDRO','GOMEZ','08/15/1961')" ); sentencia.execute( "INSERT INTO AMIGOS " + "VALUES('GONZALO','PEREZ', NULL)" ); } catch( Exception e ) { System.out.println( e ); return; } System.out.println( "Creacion finalizada." ); } } Las partes más interesantes del código son las que se van a revisar a continuación, profundizando en cada uno de los pasos. Lo primero que se hace es importar toda la funcionalidad de JDBC, a través de la primera sentencia ejecutable del programa. import java.sql.*; Las siguientes líneas son las que cargan el puente JDBC-ODBC, mediante el método forName() de la clase Class. try { Class.forName( "sun.jdbc.odbc.JdbcOdbcDriver" ); } catch( Exception e ) { System.out.println( "No se pudo cargar el puente JDBC-ODBC." ); return; } En teoría esto no es necesario, ya que DriverManager se encarga de leer todos los drivers JDBC compatibles, pero no siempre ocurre así, por lo que es mejor asegurarse. El método forName() localiza, lee y enlaza dinámicamente una clase determinada. Para drivers JDBC, la sintaxis que JavaSoft recomienda de forName() es nombreEmpresa.nombreBaseDatos.nombreDriver, y el driver deberá estar ubicado en el directorio nombreEmpresa\nombreBaseDatos\nombreDriver.class a partir del directorio indicado por la variable de entorno CLASSPATH. En este caso se indica que el puente JDBC-ODBC que se desea leer es precisamente el de Sun. Si por cualquier motivo no es posible conseguir cargar JdbcOdbcDriver.class, se intercepta la excepción y se sale del programa. En este momento es la hora de echar mano de la información que puedan proporcionar las trazas ODBC. La carga del driver también se puede especificar desde la línea de comandos al lanzar la aplicación: java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver ElPrograma A continuación, se solicita a DriverManager que proporcione una conexión para una fuente de datos ODBC. El parámetro jdbc:odbc:Tutorial especifica que la intención es acceder a la fuente de datos con nombre Tutorial, Data Source Name o DSN, en la terminología ODBC. conexion = DriverManager.getConnection("jdbc:odbc:Tutorial","","" ); El segundo y tercer parámetro son el nombre del usuario y la clave con la cual se intentará la conexión. En este caso el acceso es libre, para acceder como administrador del sistema en el caso de un servidor MS SQL se usa la cuenta sa o system administrator, cuya cuenta de acceso no tiene clave definida; en caso de acceder a un servidor MS Access, la cuenta del administrador es admin y también sin clave definida. Esta es la única línea que con seguridad habrá de cambiar el programador para probar sus aplicaciones. getConnection admite también una forma con un único parámetro (el URL de la base de datos), que debe proporcionar toda la información de conexión necesaria al driver JDBC correspondiente. Para el caso JDBC-ODBC, se puede utilizar la sentencia equivalente: DriverManager.getConnection ( "jdbc:odbc:SQL;UID=sa;PWD=" ); Para el resto de los drivers JDBC, habrá que consultar la documentación de cada driver en concreto. Inmediatamente después de obtener la conexión, en la siguiente línea sentencia = conexion.createStatement(); se solicita que proporcione un objeto de tipo Statement para poder ejecutar sentencias a través de esa conexión. Para ello se dispone de los métodos execute(String sentencia) para ejecutar una petición SQL que no devuelve datos o executeQuery(String sentencia) para ejecutar una consulta SQL. Este último método devuelve un objeto de tipo ResultSet. Una vez que se tiene el objeto Statement ya se pueden lanzar consultas y ejecutar sentencias contra el servidor. A partir de aquí el resto del programa realmente es SQL «adornado»: en la línea: sentencia.execute( "DROP TABLE AMIGOS" ); se ejecuta DROP TABLE AMIGOS para borrar cualquier tabla existente anteriormente. Puesto que este ejemplo es una aplicación «de instalación» y es posible que la tabla AMIGOS no exista, dando como resultado una excepción, se aísla la sentencia.execute() mediante un try y un catch. La línea siguiente ejecuta una sentencia SQL que crea la tabla AMIGOS con tres campos: NOMBRE, APELLIDOS y CUMPLE. De ellos, únicamente el tercero, correspondiente al cumpleaños, es el que puede ser desconocido, es decir, puede contener valores nulos. sentencia.execute( "CREATE TABLE AMIGOS ("+ " NOMBRE VARCHAR(15) NOT NULL, " + " APELLIDOS VARCHAR(30) NOT NULL, " + " CUMPLE DATETIME) " ); Y ya en las líneas siguientes se ejecutan sentencias INSERT para rellenar con datos la tabla. En todo momento se ha colocado un try ... catch exterior para interceptar cualquier excepción que puedan dar las sentencias. En general, para java.sql está definida una clase especial de excepciones que es SQLException. Se obtendrá una excepción de este tipo cuando ocurra cualquier error de proceso de JDBC, tanto si es a nivel JDBC como si es a nivel inferior (ODBC o de protocolo). Por ejemplo, si en lugar de GONZALO en la línea correspondiente a la última inserción en la Base de Datos, se intenta añadir un nombre nulo (NULL), se generará una excepción SQLException con el mensaje [Microsoft][ODBC SQL Server Driver][SQL Server]Attempt to insert the value NULL into column 'NOMBRE', table 'master.dbo.AGENDA'; column does not allow nulls. INSERT fails. que en el caso de Microsoft Access sería: [Microsoft][ODBC Microsoft Access 97 Driver] The field 'AGENDA.NOMBRE' can't contain a Null value because the Required property for this field is set to True. Enter a value in this field. En román paladino, el hecho de que la columna NOMBRE esté definida como NOT NULL, hace que no pueda quedarse vacía. Ahora se verán los pasos que hay que dar para obtener información a partir de una base de datos ya creada. Como se ha dicho anteriormente, se utilizará executeQuery en lugar de execute para obtener resultados. Se sustituyen las líneas que contenían esa sentencia por : resultado = sentencia.executeQuery( "SELECT * FROM AMIGOS" ); while( resultado.next() ) { String nombre = resultado.getString( "NOMBRE" ); String apellidos = resultado.getString( "APELLIDOS" ); String cumple = resultado.getString( "CUMPLE" ); System.out.println( "El aniversario de D. " + nombre + " " + apellidos + ", se celebra el " + cumple ); } En este caso, en la primera línea se utiliza executeQuery para obtener el resultado de SELECT * FROM AMIGOS. Mediante resultado.next() la posición se situará en el «siguiente» elemento del resultado, o bien sobre el primero si todavía no se ha utilizado. La función next() devuelve true o false si el elemento existe, de forma que se puede iterar mediante while ( resultado.next() ) para tener acceso a todos los elementos. A continuación, en las líneas siguientes se utilizan los métodos getXXX() de resultado para tener acceso a las diferentes columnas. El acceso se puede hacer por el nombre de la columna, como en las dos primeras líneas, o bien mediante su ubicación relativa, como en la última línea. Además de getString() están disponibles getBoolean(), getByte(), getDouble(), getFloat(), getInt(), getLong(), getNumeric(), getObject(), getShort(), getDate(), getTime() y getUnicodeStream(), cada uno de los cuales devuelve la columna en el formato correspondiente, si es posible. Después de haber trabajado con una sentencia o una conexión es recomendable cerrarla mediante sentencia.close() o conexión.close(). De forma predeterminada los drivers JDBC deben hacer un COMMIT de cada sentencia. Este comportamiento se puede modificar mediante el método Connection.setAutoCommit( boolean nuevovalor). En el caso de que se establezca AutoCommit a false, será necesario llamar de forma explícita a Connection.commit() para guardar los cambios realizados o Connection.rollback() para deshacerlos. Como el lector habrá podido comprobar hasta ahora, no hay nada intrínsecamente difícil en conectar Java con una base de datos remota. Los posibles problemas de conexión que puede haber (selección del driver o fuente de datos adecuada, obtención de acceso, etc.), son problemas que se tendrían de una u otra forma en cualquier lenguaje de programación. El objeto ResultSet devuelto por el método executeQuery(), permite recorrer las filas obtenidas, no proporciona información referente a la estructura de cada una de ellas; para ello se utiliza ResultSetMetaData, que permite obtener el tipo de cada campo o columna, su nombre, si es del tipo autoincremento, si es sensible a mayúsculas, si se puede escribir en dicha columna, si admite valores nulos, etc. Para obtener un objeto de tipo ResultSetMetaData basta con llamar al método getMetaData() del objeto ResultSet. En la lista siguiente aparecen algunos de los métodos más importantes de ResultSetMetaData, que permiten averiguar toda la información necesaria para formatear la información correspondiente a una columna, etc. getCatalogName() getColumnName() getColumnLabel() getColumnDisplaySize() getColumnCount() getTableName() getPrecision() getScale() getColumnType() getColumnTypeName() isSigned() isAutoIncrement() isCurrency() isCaseSensitive() isNullable() isReadOnly() isWritable() isDefinitivelyWritable() isSearchable() getSchemaName() En general pues, los objetos que se van a poder encontrar en una aplicación que utilice JDBC, serán los que se indican a continuación. Connection DriverManager Statement PreparedStatement
CallableStatement
ResultSet
Contiene la tabla resultado de la pregunta SQL que se haya realizado. En párrafos anteriores se han comentado los métodos que proporciona este objeto para recorrer dicha tabla.
|
|