JDBC

La aplicación de subasta con JavaBeans Enterpise y con sus dos variantes de "Remote Method Invocation" (RMI) y "Common Object Request Broker" (CORBA) han usado llamadas sencillas de JDBC JDBCTM para actualizar y consultar información desde una base de datps usando una conexión JDBC. Por defecto, el acceso a bases de datos JDBC implica abrir una conexión con la base de datos, ejecutar comandos SQL en un sentencia, procesar los datos devueltos y cerrar la conexión con la base de datos.

En conjunto, la aproximación por defecto funciona bien para bajos volúmenes de acceso a la base de datos, pero ¿cómo podemos manejar un gran número de peticiones que actualizan muchas tablas relacionadas a la vez y aún así asegurar la integridad de los datos? Esta sección explica cómo hacerlo con los siguientes tópicos:


Drivers JDBC

La conexión con la base de datos está manejada por la clase Driver JDBC. El SDK de Java contiene sólo un driver JDBC, un puente jdbc-odbc que comunica con un driver "Open DataBase Conectivity" (ODBC) existente. Otras bases de datos necesitan un driver JDBC espécifico para esa base de datos.

Para obtener un idea general de lo que hacer un driver JDBC, podemos examinar el fichero JDCConnectionDriver.java. La clase JDCConnectionDriver implemta la clase java.sql.Driver y actúa como un driver "pass-through" re-enviando peticiones JDBC al driver JDBC real de la base de datos. La clase driver JDBC se carga con un llamada a Class.forName(drivername).

Estas líneas de código muestran cómo cargar tres clases diferentes de drivers JDBC:

  Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
  Class.forName("postgresql.Driver"); 
  Class.forName("oracle.jdbc.driver.OracleDriver");
Cada driver JDBC está configurado para entender una URL específica, por eso se pueden cargar varios drivers JDBC a la vez. Cuando especificamos una URL en el momento de la conexión, se selecciona el primer driver JDBC que corresponda.

El puente jdbc-odbc acepta URLS que empiecen con jdbc:odbc: y usa el siguiente campo de esa URL para especificar el nombre de la fuente de los datos. Este nombre identifica el esquema de la base de datos particular a la que queremos acceder. La URL también puede incluir más detalles sobre cómo contactyar con la base de datos e introducir la cuenta.

//access the ejbdemo tables
  String url = "jdbc:odbc:ejbdemo";
El siguiente ejemplo contiene la información de Oracle SQL*net sobre una base de datos particular llamada ejbdemo en la máquina dbmachine:
  String url = "jdbc:oracle:thin:user/password@(
	description=(address_list=(
	               address=(protocol=tcp)
	(host=dbmachine)(port=1521)))(source_route=yes)
	(connect_data=(sid=ejbdemo)))";
Este siguiente ejemplo usa mysql para conectar con la base de datos ejbdemo en la máquina local. También se incluyen los detalles del nombre de usuario y la password para el login.
  String url = 
   "jdbc:mysql://localhost/ejbdemo?user=user;
      password=pass";
Los drivers JDBC se dividen en cuatro tipos. También se pueden categorizar como puro java o drivers pequeños para indicar si son usados por aplicaciones clientes (drivers puro java) o por applets (drivers pequeños).

Drivers del Tipo 1

Los drivers JDBC del tipo 1 son drivers puente como el puente jdbc.odbc. Estos drivers utilizan un intermediario como el ODBC para transferir las llamadas SQL a la base de datos. Los drivers puente cuentan con código nativo, aunque la librería de código nativo del puente jdbc-odbc forma parte de la Máquina Virtual Java 21.

Drivers del Tipo 2

Los drivers del tipo 2 usan el API existente de la base de datos para comunicarla con el cliente. Aunque los drivers del tipo 2 son más rápidos que los del tipo 1, los del tipo 2 usan código nativo y requieren permisos adicionales para funcionar en un applet.

Un driver del tipo 2 podría necesitar código de base de datos en el lado del cliente para conectar a través de la red.

Drivers del Tipo 3

Los Drivers del tipo 3 llaman al API de la base de datos en el servidor. Las peticiones JDBC desde el cliente son primero comprobadas por el Driver JDBC en el servidor para ejecutarse. Los drivers del tipo 3 y 4 pueden usarse en clientes applets ya que no necesitan código nativo.

Driveres del Tipo 4

El nivel más alto de drivers reimplementa el API de red para base de datos en el lenguaje Java. Los Drivers del tipo 4 también pueden usarse en clientes applets porque no necesitan código nativo.

Conexiones a Bases de Datos

Una conexión con una base de datso puede establecerese con un llamada al método DriverManager.getConnection. La llamada toma una URL que identifica la base de datos, y opcionalmente el nombre de usuario y la password para la base de datos.
  Connection con = DriverManager.getConnection(url);
  Connection con = DriverManager.getConnection(url, 
			"user", "password");
Después de establecer la conexión, se puede ejecutar una sentencia contra la base de datos. Los resultados de la sentencias pueden recuperarse y cerrarse la conexión.

Una características útil de la clase DriverManager es el método setLogStream. Podemos usar este método para generar información de seguimiento para ayudarnos a dignosticar problemas de conexión que normalmente no serían visibles. Para generar la información de seguimiento, sólo tenemos que llamar al método de esta forma:

  DriverManager.setLogStream(System.out);
La sección Connection Pooling en el capítulo 8 muestra cómo podemos mejorar las conexión JDBC sin cerrrar la conexión una vez completada la sentencia. Cada conexión JDBC a una base de datos provoca una sobrecarga al abrir un nuevo socket y usar el nombre de usuario y la password para login en la base de datos. La reutilización de las conexiones reduce la sobrecarga. Las colas de Conexiones mantienen una lista de conexiones abiertas y limpia cualquier conexión que no pueda ser reutilizada.

Sentencias

Hay tres tipos básicos de sentencias SQL usadas en el API JDBC: CallabelStatement, Statement, y PreparedStatement. Cuando se envía una sentencias Statement o PreparedStatement a la base de datos, el driver la traduce a un formato que la base de datos pueda reconocer.

Sentencias Callable

Una vez que hemos establecido una conexión con una base de datos, podemos usar el método Connection.prepareCall para crear una sentencia callable. Estas sentencias nos permite ejecutar prodecimientos almacenados SQL.

El siguiente ejemplo crea un objeto CallableStatement con tres parámetros para almacenar información de la cuenta de login:

  CallableStatement cs =
        con.prepareCall("{call accountlogin(?,?,?)}");
  cs.setString(1,theuser);
  cs.setString(2,password);
  cs.registerOutParameter(3,Types.DATE);

  cs.executeQuery();
  Date lastLogin = cs.getDate(3);

Statements

El interface Statement nos permite ejecutar una simple sentencias SQL sin parámetros. Las instrucciones SQL son insertadas dentro del objeto Statement cuando se llama al método Statement.executeXXX method.

Sentencias Query: Este segmento de código crea un objeto Statement y llama al método Statement.executeQuery para seleccionar texto desde la base de datos dba. El resultado de la consulta se devuelve en un objeto ResultSet. Cómo recuperar los resultados desde este objeto ResultSet se explica más abajo en Hoja de Resultados.

 Statement stmt = con.createStatement();
 ResultSet results = stmt.executeQuery(
                       "SELECT TEXT FROM dba ");

Sentencias Update: Este segmento de código crea un objeto Statement y llama al método Statement.executeUpdate para añadir una dirección de email a una tabla de la base de datos dba:

  String updateString = 
           "INSERT INTO dba VALUES (some text)";
  int count = stmt.executeUpdate(updateString);

Setencias Prepared

El interface PreparedStatement desciende del interface Statement y usa una plantilla para crear peticiones SQL. Se usa una PreparedStatement para enviar sentencias SQL precompiladas con uno o más parámetros.

Query PreparedStatement: Creamos un objeto PreparedStatement especificando la definición de plantilla y la situación de los parámetros. Los datos de los parámetros se insertan dentro del objeto PreparedStatement llamando a sus métodos setXXX y especificando el parámetro y su dato. Las instrucciones SQL y los parámetros son enviados a la base de datos cuando se llama al método executeXXX.

Este segmento de código crea un objeto PreparedStatement para seleccionar datos de usuarios basados en la dirección email del usuario. El interrogante ("?") indica que este sentencia tiene un parámetro:

  PreparedStatement pstmt = con.prepareStatement("
                              select theuser from 
                              registration where 
                              emailaddress like ?");
//Initialize first parameter with email address
  pstmt.setString(1, emailAddress);
  ResultSet results = ps.executeQuery();

Una vez que se ha inicializado la plantilla PreparedStatement sólo se insertan los valores modificados para cada llamadas:

  pstmt.setString(1, anotherEmailAddress);

Nota: No todos los drivers de bases de datos compilan sentencias preparadas.

Update PreparedStatement: Este segmento de código crea un objeto PreparedStatement para actualizar el registro de un vendedor. La plantilla tiene cinco parámetros, que se seleccionan con cinco llamadas a los métodos PreparedStatement.setXXX apropiados.

PreparedStatement ps = con.prepareStatement(
	"insert into registration(theuser, password, 
           emailaddress, creditcard, 
           balance) values (
           ?, ?, ?, ?, ?)");
  ps.setString(1, theuser);
  ps.setString(2, password);
  ps.setString(3, emailaddress);
  ps.setString(4, creditcard);
  ps.setDouble(5, balance);
  ps.executeUpdate();

Cachear los Resultados de la Base de Datos

El concepto PreparedStatement de reutilizar peticiones puede extenderse al cacheo de resultados de una llamada JDBC. Por ejemplo, una descripción de un ítem de la subastas permanece igual hasta que el vendedor lo cambia. Si el ítem recibe cientos de peticiones, el resultado de la sentencia: query "select description from auctionitems where item_id='4000343'" podría ser almacenado de forma más eficiente en un tabla hash.

Almacenar resultados en una tbal hash requiere que la llamada JDBC sea interceptada antes de crear una sentencia real que devuelva los resultados cacheados, y la entrada del caché debe limpiarse si hay una actualización correspondiente con ese item_id.

Hoja de Resultados

El interface ResultSet maneja accesos a datos devueltos por una consulta. Los datos devueltos son igual a una línea de la base de la tabla de la base de datos. Algunas consultas devuelven una línea, mientras que muchas consultas devuelven múltiples líneas de datos.

Se utilizan los métodos getType para recuperar datos desde columnas específicas para cada fila devuelta en la consulta. Este ejemplo recupera la columna TEXT de todas las tablas con una columna TEXT en la base de datosdba. El método results.next mueve hasta la siguiente fila recuperada hasta que se hayan procesado todas las filas devueltas:

  Statement stmt = con.createStatement();
  ResultSet results = stmt.executeQuery(
                        "SELECT TEXT FROM dba ");
  while(results.next()){
    String s = results.getString("TEXT");
    displayText.append(s + "\n");
  }
  stmt.close();

Hoja de Resultados Scrollable

Antes del JDBC 2.0, los dirvers JDBC devolvían hojas de resultado de sólo lectura con cursores que sólo se movían en una dirección, hacia adelante. Cada elemento era recuperado mediante una llamada al método next de la hoja de resultados.

JDBC 2.0 introduce las hojas de resultados scrollables cuyos valores pueden ser leídos y actualizados si así lo permite la base de datos original. Con las hojas de resultados scrollables, cualquier fila puede ser seleccionada de forma aleatorio, y nos podemos mover por la hoja de resultados hacia adelante y hacia atrás.

Una ventaja de la nueva hoja de resultados es que podemos actualizar un conjunto de filas correspondientes son tener que enviar una llamada adicional a executeUpdate. Las actualizaciones se hacen llamando a JDBC y no se necesitan comandos SQL personalinzados. Esto aumenta la portabilidad del código de la base de datos que creemos.

Tanto Statements como PreparedStatements tienen un constructor adicional que acepta un parámetro tipo scroll y otro tipo update. El valor del tipo scroll puede ser uno de los siguientes valores:

El parámetro del tipo update puede ser uno de estos dos valores: Podemos verificar que nuestra base de datos soporta estos tipos llamando al método con.getMetaData().supportsResultSetConcurrency() como se ve aquí:
  Connection con = getConnection();
  if(con.getMetaData().supportsResultSetConcurrency(
     ResultSet.SCROLL_INSENSITIVE,
     ResultSet.CONCUR_UPDATABLE)) {

     PreparedStatement pstmt = con.prepareStatement( 
       "select password, emailaddress,
       creditcard, balance from 
       registration where theuser = ?", 
       ResultSet.SCROLL_INSENSITIVE,
       ResultSet.CONCUR_UPDATABLE); 
 } 

Navegar por la Hoja de Resultados

La hoja de resultados totalmente scrollable devuelve un cursor que puede moverse usando comandos sencillos. Por defecto el cursor de la hoja de resultados apunta a la fila antes de la primera fila en la hoja de resultados. Una llamada a next() recupera la primera fila de la hoja de resultados. el cursor puede tambien moverse llamando a uno de los siguientes métodos de ResultSet:

Actualizar la Hoja de Resultados

Podemos actualizar un valor en la hoja de resultados llamando al método ResultSet.update<type> sobre la fula donde está posicionado el cursor. El valor del tipo aquí es el midmo usando cuando se recupera un valor de una hoja de resultados, por ejemplo, updateString actualiza un valor String en la hoja de resultados.

El siguiente código actualiza el balance de un usuario desde la hoja de resultados creada anteriormente. La actualización sólo se aplica a la hoja de resultados hasta que se llama a rs.updateRow(), que actualiza la base de datos original. Cerrando la hoja de resultados antes de llamar a updateRow se perderá cualquier edición aplicada en la hoja de resultados.

  rs.first();
  updateDouble("balance", 
	rs.getDouble("balance") - 5.00);
Insertar una nueva fila usa los mismos métodos update<type>. La única diferencia es que se llama al método rs.moveToInsertRow de que los datos hayan sido inicializados y después se llama a rs.insertRow(). Podemos borrar la fila actual con una llamada a rs.deleteRow().

Trabajos Batch

Por defecto, cada sentencia JDBC se envía individualmente a la base de datos. Aparte de las peticiones de red adicionales, este proceso provoca retrasos adicionales si la transación expande varias sentencias. JDBC 2.0 nos permite envíar varias sentencias a la vez con el método addBatch.

El siguiente método muestra cómo usar la sentencia addBatch. Las llamadas a stmt.addBatch añaden sentencias a la Statement original, y la llamada a executeBatch envía la sentencia completa con todos los apéndices a la base de datos.

   Statement stmt = con.createStatement();
   stmt.addBatch(
	"update registration set balance=balance-5.00
        where theuser="+theuser);
   stmt.addBatch(
	"insert into auctionitems(
	               description, startprice) 
        values("+description+","+startprice+")");
 
   int[] results = stmt.executeBatch();
La hoja de resultados del método addBatch es un array de cuentas de filas afectadas por cada sentencia ejecutada en el trabajo batch. Si ocurre un problema se lanzará una java.sql.BatchUpdateException. Se puede obteener un array incompleto de contador de fila de BatchUpdateException llamando a su método getUpdateCounts.

Almacenar Clases, Imágenes y otros Objetos Grandes

Muchas bases de datos pueden almacenar datos binarios como parte de una fila si el campo es asignado como long raw, longvarbinary, u otro tipo similar. Esto campos pueden ocupar hasta 2 Gigabytes de datos. Esto significa que podemos convertir los datos en un stram binario o un array de bytes, puede ser almacenado o recuperado desde una base de datos como lo sería un string o un double.

Esta técnica peude usarse para almacenar y recuperar imágenes y objetos Java.

Almacenar y recuperar una imagen: Es muy fácil almacenar un objeto que puede ser serializado o convertido en un array de bytes. Desafortunadamente java.awt.Image no es Serializable. Sin embargo, como se ve en el siguiente ejemplo de código, podemos almacenar los datos de la imagen en un fichero y almacenar la información del fichero como bytes en un campo binario de la base de datos.

  int itemnumber=400456;

  File file = new File(itemnumber+".jpg");
  FileInputStream fis = new FileInputStream(file);
  PreparedStatement pstmt = con.prepareStatement(
	"update auctionitems 
        set theimage=? where id= ?");
  pstmt.setBinaryStream(1, fis, (int)file.length()):
  pstmt.setInt(2, itemnumber);
  pstmt.executeUpdate();
  pstmt.close();
  fis.close();
Para recuperar esta imagen y crear un array de bytes que pueda ser pasado a createImage, hacemos los siguiente:
  int itemnumber=400456;
  byte[] imageBytes;
 
  PreparedStatement pstmt = con.prepareStatement(
    "select theimage from auctionitems where id= ?");
  pstmt.setInt(1, itemnumber);
  ResultSet rs=pstmt.executeQuery();
  if(rs.next()) {
    imageBytes = rs.getBytes(1);
  }
  pstmt.close();
  rs.close();

  Image auctionimage = 
	Toolkit.getDefaultToolkit().createImage(
	                              imageBytes);
Almacenar y Recuperar un Objeto: Una clase puede ser serializada a un campo binario de la base de datos de la misma forma que se hizo con la imagen en el ejemplo anterior. En este ejemplo, la clase RegistrationImpl se ha modificado para soportar la serialización por defecto añadiendole implements Serializable a la declaración de la clase.

Luego, se crea un array ByteArrayInputStream para pasarlo como un Stream Binario a JDBC. Para crear el ByteArrayInputStream, RegistrationImpl primero pasa a través de un ObjectOutputStream hacia el ByteArrayInputStream con una llamada a RegistrationImpl.writeObject. Luego el ByteArrayInputStream es convertido a un array de bytes, que puede ser utilizado para crear el ByteArrayInputStream. El método create en RegistrationServer.java se ha modificado de esta forma:

  public registration.RegistrationPK create(
	String theuser, 
	String password, 
	String emailaddress, 
	String creditcard) 
	throws registration.CreateException{

     double balance=0;
     Connection con = null;
     PreparedStatement ps = null;;

     try {
        con=getConnection();
        RegistrationImpl reg= new RegistrationImpl();
        reg.theuser = theuser;
        reg.password = password;
        reg.emailaddress = emailaddress;
        reg.creditcard = creditcard;
        reg.balance = balance;

        ByteArrayOutputStream regStore = 
		new ByteArrayOutputStream();
        ObjectOutputStream regObjectStream = 
		new ObjectOutputStream(regStore);
           regObjectStream.writeObject(reg);

        byte[] regBytes=regStore.toByteArray();
        regObjectStream.close();
        regStore.close();
        ByteArrayInputStream regArrayStream = 
		new ByteArrayInputStream(regBytes);
        ps=con.prepareStatement(
		"insert into registration (
		theuser, theclass) values (?, ?)");
        ps.setString(1, theuser);
        ps.setBinaryStream(2, regArrayStream, 
             regBytes.length);

        if (ps.executeUpdate() != 1) {
            throw new CreateException ();
        }
        RegistrationPK primaryKey = 
                         new RegistrationPKImpl();
        primaryKey.theuser(theuser);
        return primaryKey;
     } catch (IOException ioe) {
       throw new CreateException ();
     } catch (CreateException ce) {
       throw ce;
     } catch (SQLException sqe) {
       System.out.println("sqe="+sqe);
       throw new CreateException ();
     } finally {
       try {
          ps.close();
         con.close();
       } catch (Exception ignore) {
       }
     }
  }
El objeto es recuperado y resconstruido extrayendo los bytes desde la base de datos, creando un ByteArrayInputStream desde aquellos bytes leídos desde un ObjectInputStream, y llamando a readObject para crear de nuevo el ejemplar.

El siguiente ejemplo muestra los cambios necesarios en el método RegistrationServer.refresh para recuperar el ejemplar Registration desde la base de datos.

  private Registration refresh(RegistrationPK pk)
		throws FinderException {

      if (pk == null) {
          throw new FinderException ();
      }
      Connection con = null;
      PreparedStatement ps = null;
      try {
         con=getConnection();
         ps=con.prepareStatement("
		select theclass from 
                registration where theuser = ?");
         ps.setString(1, pk.theuser());
         ps.executeQuery();
         ResultSet rs = ps.getResultSet();
         if(rs.next()){
            byte[] regBytes = rs.getBytes(1);
            ByteArrayInputStream regArrayStream = 
                new ByteArrayInputStream(regBytes);
            ObjectInputStream regObjectStream = 
                new ObjectInputStream(
                      regArrayStream);
            RegistrationImpl reg= 
                (RegistrationImpl)
                       regObjectStream.readObject();
            return reg;
         }
         else {
             throw new FinderException ();
         }
     } catch (Exception sqe) {
         System.out.println("exception "+sqe);
         throw new FinderException ();
     }
     finally {
        try {
           rs.close();
           ps.close();
           con.close();
        }
        catch (Exception ignore) {}
     }
  }

BLOBs y CLOBs: Almacenar grandes campos en un tabla con otros datos no es necesariamente el lugar óptimo especialmente si los datos tienen un tamaño variable. una forma de manejar objetos de tamaño grande y variable es con el tipo "Large Objects" (LOBs). Este tipo usa un localizador, esencialmente un puntero, en el registro de la base de datos que apunta al campo real en la base de datos.

Hay dos tipos de LOBs: "Binary Large Objects" (BLOBs) y "Character Large Objects" (CLOBs). Cuando accedemos a BLOB o CLOB, los datos no se copian en el cliente. Para recuperar los datos reales desde una hoja de resultados, tenemos que recuperar el puntero con una llamada a BLOB blob=getBlob(1) o CLOB clob=getClob(1), y luego recuperar los datos con una llamada a blob.getBinaryStream() o clob.getBinaryStream().

Controlar Transaciones

Por defecto, las sentencias JDBC son procesadas en el modo full auto-commit. Este modo funciona bien para una sola consulta a la base de datos, pero si la operación depende de varias sentencias de la base de datos que todas deben completarse con éxito o toda la operación será cancelada, se necesita una transación más adecuada.

Una descripción de los niveles de aislamiento en la transaciónse cubre con más detalles en el Capítulo 3: Maneja¡o de Datos y Transaciones. Para usar control de transaciones en la plataforma JDBC, primero necesitamos desactivar el moco "full auto-commit" llamando a:

  Connection con= getConnection();
  con.setAutoCommit(false);
En este punto, podemos enviar cualquier siguiente sentencia JDBC o deshacer cualquier actualización llamando al método Connection.rollback. La llamada rollback se sitúa normalmente en el manejador de excepciones, aunque puede situarse en cualquier lugar en le flujo de la transación.

El siguiente ejemplo inserta un ítem en la subasta y decrementa el balance del usuario. Si el balance es menor de cero, se deshace la transación complera y el ítem de susbasta es eliminado.

  public int insertItem(String seller, 
	String password, 
	String description, 
	int auctiondays, 
	double startprice, 
	String summary) {
     Connection con = null;
     int count=0;
     double balance=0;
     java.sql.Date enddate, startdate;
     Statement stmt=null;

     PreparedStatement ps = null;
     try {
       con=getConnection();
       con.setAutoCommit(false);
       stmt= con.createStatement();
       stmt.executeQuery(
              "select counter from auctionitems");
       ResultSet rs = stmt.getResultSet();
       if(rs.next()) {
         count=rs.getInt(1);
       }
       Calendar currenttime=Calendar.getInstance();
       java.util.Date currentdate=currenttime.getTime();
       startdate=new java.sql.Date(
                       currentdate.getTime());
       currenttime.add(Calendar.DATE, auctiondays);
       enddate=new java.sql.Date((
		currenttime.getTime()).getTime());

       ps=con.prepareStatement(
                "insert into auctionitems(
		id, description, startdate, enddate, 
		startprice, summary) 
		values (?,?,?,?,?,?)");
       ps.setInt(1, count);
       ps.setString(2, description);
       ps.setDate(3, startdate);
       ps.setDate(4, enddate);
       ps.setDouble(5, startprice);
       ps.setString(6, summary);
       ps.executeUpdate();
       ps.close();

       ps=con.prepareStatement(
		"update registration 
		set balance=balance -0.50 
		where theuser= ?");
       ps.setString(1, seller);
       ps.close();
       stmt= con.createStatement();
       stmt.executeQuery(
		"select balance from registration 
		where theuser='"+seller+"'");
       rs = stmt.getResultSet();
       if(rs.next()) {
         balance=rs.getDouble(1);
       }
       stmt.close();
       if(balance <0) {
         con.rollback();
         con.close();
         return (-1);
       }

       stmt= con.createStatement();
       stmt.executeUpdate(
		"update auctionitems set 
		counter=counter+1");
       stmt.close();
       con.commit();
       con.close();
       return(0);
     } catch(SQLException e) {
       try {
         con.rollback();
         con.close();
         stmt.close();
         ps.close();
       }catch (Exception ignore){}
     }
     return (0);
  }

Caracteres de Escape

El API JDBC proporciona la palabr clave escape para que podamos especificar el caracter que querramos usar como caracter de escape. Por ejemplo, si queremos usar el signo de tanto por ciento (%) como el símbolo de tanto por ciento que que no se interprete como un comodín SQL usando en consultas SQL LIKE, tenemos que escaparlo con el caracter de escape que especifiquemos con la palabra clave escape.

La siguiente sentencia muestra cómo podemos usar la palabra clave escape para buscar por el valor 10%:

  stmt.executeQuery(
   "select tax from sales where tax like 
       '10\%' {escape '\'}");
Si nuestro programa almacena nombres y direcciones en la base de datos introducidos desde la línea de comandos o desde un interface de usuario, el símbolo de comilla simple (') podría aparecer en los datos. Pasar una comilla simple directamente a un string SQL causa problemas cuando la sentencia es analizada porque SQL le da a este símbolo otro significado a menos que se le escape.

Para resolver este problem, el siguiente método escapa cualquier símbolo ' encontrado en la línea de entrada. Este método puede ser extendido para escapar cualquier otro caracter como las comas , que la base de datos o su driver podrían interpretar de otra forma:

static public String escapeLine(String s) {
  String retvalue = s;
  if (s.indexOf ("'") != -1 ) {
    StringBuffer hold = new StringBuffer();
    char c;
    for(int i=0; i < s.length(); i++ ) {
      if ((c=s.charAt(i)) == '\'' ) {
      hold.append ("''");
    }else {
      hold.append(c);
    }
  }
  retvalue = hold.toString();
  }
  return retvalue;
}
Sin embargo, si usamos un PreparedStatement en lugar de una simple Statement, muchos de estos problemas de escape desaparecen. Por ejemplo, en lugar de esta línea con la secuencia de escape:
stmt.executeQuery(
"select tax from sales where tax like 
     '10\%' {escape '\'}");
Podríamos usar esta línea:
preparedstmt = C.prepareStatement(
                  "update tax set tax = ?");

Mapear Tipos de Base de Datos

Aparte de unos pocos tipos como INTEGER que son representados como INTEGER en las bases de datos más populares, podríamos encontrar que el tipo JDBC de una columna de la tabla no corresponde con el tipo representado en la base de datos. Esto significa que lllamar a ResultSet.getObject, PreparedStatement.setObject y CallableStatement.getObject() fallará bastantes veces.

Nuestro programa puede determinar los tipos de las columnas de la base de datos desde los datos meta de la base de datos y usar esta información para chequear el valor antes de recuperarlo. Este código chequea que el valor es del tipo INTEGER antes de recuperarlo.

  int count=0;
  Connection con=getConnection();
  Statement stmt= con.createStatement();
  stmt.executeQuery(
         "select counter from auctionitems");
  ResultSet rs = stmt.getResultSet();
  if(rs.next()) {
    if(rs.getMetaData().getColumnType(1) == 
                             Types.INTEGER) {
    Integer i=(Integer)rs.getObject(1);
    count=i.intValue();
    }
  }
  rs.close();

Mapeo de Tipos Date

El tipo DATE es donde ocurren más errores. Es porque la clase java.util.Date representa tanto la Fecha como la Hora, pero SQL tiene estos tres tipos para representar informaciónde fecha y hora: Estos tres tipos adiciones los proporciona el paquete java.sql como java.sql.Date, java.sql.Time y java.sql.Timestamp y son todos suclases de java.util.Date. Esto significa que podemos usar valores java.util.Date convertidos al tipo necesario para que sean compatibles con el tipo de la base de datos.


Nota: la clase Timestamp pierde precisión cuando se convierte a java.util.Date porque java.util.Date no contiene un campo de nanosegundos, es mejro no convertir un ejemplar Timestamp si el valor va a ser escrito de vuelta en la base de datos.

Este ejemplo usa la clase java.sql.Date para convertir el valor java.util.Date devuelto por la llamada a Calendar.getTime hacia java.sql.Date.

  Calendar currenttime=Calendar.getInstance();
  java.sql.Date startdate=
     new java.sql.Date((
           currenttime.getTime()).getTime());
También podemo usar la clase java.text.SimpleDateFormat para hacer la conversión. Este ejemplo usa la clase java.text.SimpleDateFormat para convertir un objeto java.util.Date a un objeto java.sql.Date:
  SimpleDateFormat template = 
	new SimpleDateFormat("yyyy-MM-dd"); 
  java.util.Date enddate = 
	new java.util.Date("10/31/99"); 
  java.sql.Date sqlDate = 
	java.sql.Date.valueOf(
	                template.format(enddate)); 
Si encontramos que una representación de fecha de una base de datos no puede ser mapeada a un tipo Java con una llamada a getObject o getDate, recuperamos el valor con una llamada a getString y formateamos el string como un valor Date usando la clase SimpleDateFormat mostrada arriba.

_______
1 Cuando se usan en toda esta site, los términos, "Java virtual machine" o "JVM" significa una máquina virtual de la plataforma Java.


Ozito