< Anterior | Siguiente >

Lección 7: Manejo de excepciones y retrotracciones

Es especialmente importante manejar adecuadamente los errores de las aplicaciones que acceden a orígenes de datos para evitar recuperar datos incorrectos, insertar datos incorrectos o generar incoherencias entre la aplicación y e origen de datos. En esta lección obtendrá información acerca de algunas herramientas que puede utilizar para responder a los errores de acceso a bases de datos relacionales.
Por qué y cuándo se efectúa esta tarea

Manejo de excepciones de acceso a datos SQL

Probablemente habrá observado que, en los ejemplos de esta guía de aprendizaje, las sentencias de acceso a datos se colocan con frecuencia dentro de un bloque try.
Por qué y cuándo se efectúa esta tarea
function execDropTable(status StatusRec inOut)
  try  
    execute #sql{
      DROP TABLE EGL.CUSTOMERTEMP
      } ;
  onException (exception SQLException)
    ConditionHandlingLib.HandleException(status, exception);
  end
end
Cuando se coloca código en un bloque try, EGL intenta ejecutar ese código como es habitual. Si EGL encuentra errores, detiene la ejecución del código en el bloque try y se desplaza directamente al bloque onException, pasando por alto las sentencias restantes del bloque try. Puede colocar código en el bloque onException para realizar la recuperación del error, corregir el problema o escribir información de error en el archivo de registro.

Cuando EGL encuentra un error dentro de un bloque try, también crea un registro de excepción para suministrar información acerca del error. Pasa ese registro al bloque onException. Dentro del bloque onException, puede utilizar la información de ese registro para facilitar la determinación de la causa del error y realizar su recuperación. En el ejemplo anterior, el bloque onException pasa el registro de excepción a una función que trata con el error.

Los registros de excepción pueden tener uno o varios estereotipos, dependiendo del tipo de error. En el ejemplo anterior, el bloque onException espera un registro de excepción con el estereotipo SQLException, que está destinado a errores de SQL y a otros errores de base de datos relacional. Si EGL encuentra un tipo de error diferente, como por ejemplo una referencia a un valor nulo, crea un tipo de registro de excepción diferente (en el caso de una referencia a un valor nulo, un registro de excepción con el estereotipo NullValueException).

Puede colocar varios bloques onException a continuación de un único bloque try a fin de procesar tipos de errores diferentes, como en el ejemplo siguiente:
function execDropTable(status StatusRec inOut)
  try  
    execute #sql{
      DROP TABLE EGL.CUSTOMERTEMP
      } ;
  onException (exception SQLException)
    ConditionHandlingLib.HandleException(status, exception);
  onException (exception AnyException)
		HandleOtherError(exception);
  end
end
En este ejemplo, el primer bloque onException espera un registro SQLException y el segundo bloque espera el estereotipo AnyException. En este caso, si EGL encuentra un error de SQL, se ejecuta el primer bloque onException; si EGL encuentra cualquier otro tipo de error, se ejecuta el segundo bloque. El estereotipo AnyException es un estereotipo de registro de excepción de usuario genérico utilizado para responder a cualquier error independientemente del estereotipo.

Todos los registros de excepción tienen como mínimo dos campos: un campo messageID con el código del error y un campo message con una breve descripción del problema. Dependiendo del estereotipo, los registros de excepción pueden tener otros campos. Por ejemplo, el estereotipo SQLRecord tiene un campo sqlState que contiene un valor CHAR(5) con el estado de la sentencia, y un campo sqlCode que contiene un código de retorno INT de DBMS.

Puede probar el manejo de excepciones pasando información no válida, suprimiendo o actualizando un registro inexistente o intentando añadir un registro con la misma clave primaria. El ejemplo siguiente intenta añadir un registro de cliente nuevo con el mismo número de ID que el de una fila existente en la base de datos, lo cual provocará un conflicto con las claves primarias y se solicitará a EGL que lance una excepción de tipo SQLException.

  1. Cree un programa en el paquete programs con el nombre exceptionHandlingTest.
  2. En la función main del programa nuevo, cree una variable de registro de cliente y asígnele información que incluya un número de ID ya existente en la base de datos.
    package programs;
    
    import EGLDerbyDB.data.Customer;
    
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
      end
    
    end
  3. Dentro de un bloque try, añada el registro a la base de datos:
    try
      add customer;
      SysLib.writeStdout("Cliente añadido satisfactoriamente.");
    end
    Este bloque try no es de mucha utilidad, ya que no existe ningún bloque onException coincidente para responder a los errores.
  4. Antes de la palabra end que cierra el bloque try, añada un bloque onException para manejar la SQLException que se producirá cuando EGL intente ejecutar el código del bloque try:
    try
      add customer;
      SysLib.writeStdout("Cliente añadido satisfactoriamente.");
    onException(ex SQLException)
      SysLib.writeStdout("SQL exception "::ex.messageID);
      SysLib.writeStdout("message = "::ex.message);
      SysLib.writeStdout("SQLCode = "::ex.sqlCode);
      SysLib.writeStdout("SQLState = "::ex.sqlState);
    end
    El programa completo tiene este aspecto:
    package programs;
    
    import EGLDerbyDB.data.Customer;
    
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
      
        try
          add customer;
          SysLib.writeStdout("Cliente y pedido añadidos satisfactoriamente.");
        onException(ex SQLException)
          SysLib.writeStdout("SQL exception "::ex.messageID);
          SysLib.writeStdout("message = "::ex.message);
          SysLib.writeStdout("SQLCode = "::ex.sqlCode);
          SysLib.writeStdout("SQLState = "::ex.sqlState);
        end
        
            
      end
      
    end
  5. Guarde, genere y ejecute el programa. La consola muestra la salida del bloque onException:
    SQL exception EGL0504E
    message = EGL0504E ADD: La sentencia ha terminado anormalmente
      porque habría provocado un valor de clave duplicado
      en una restricción de clave exclusiva o primaria o en un
      índice exclusivo identificados por 'SQL070307095259210' definido
      en 'CUSTOMER'.[sqlstate:23505][sqlcode:20000]
    EGL0002I El error se ha producido en el
      programa exceptionHandlingTest que procesa la función main.
    SQLCode = 20000
    SQLState = 23505

Retrotraer cambios de la base de datos

La mayoría de bases de datos relacionales, incluida Derby, son recursos recuperables. Cuando se realiza un cambio en un recurso recuperable, ese cambio no es permanente hasta que se emite un mandato commit, que guarda los cambios. De ese modo, puede deshacer los cambios realizados en la base de datos si encuentra una excepción antes de comprometer dichos cambios.
Por qué y cuándo se efectúa esta tarea
Puede establecer la opción sqlCommitControl del descriptor de construcción en NOAUTOCOMMIT, que impide que EGL comprometa cada uno de los cambios automáticamente. Teniendo desactivado el compromiso automático, puede comprometer manualmente los cambios invocando a sysLib.commit() y deshacer los cambios efectuados desde el último compromiso invocando a sysLib.rollback(). Si no compromete los cambios con sysLib.commit(), EGL siempre compromete los cambios al final de la unidad ejecutable, por ejemplo al final de un programa principal (main).

Por ejemplo, imagine que está realizando varios cambios en la base de datos, como por ejemplo añadiendo un cliente nuevo y especificando información para el primer pedido de dicho cliente. Si algunas de estas operaciones han sido satisfactorias antes de que otra falle, puede quedarse con un cliente sin pedidos o con un pedido sin cliente que lo hay efectuado. En este caso, puede colocar una retrotracción (rollback) en el bloque onException para deshacer los cambios y mantener la información incompleta fuera de la base de datos:

  1. Establezca la opción sqlCommitControl del descriptor de construcción en NOAUTOCOMMIT:
    1. En la vista Explorador de proyectos, efectúe una doble pulsación en el descriptor de construcción del proyecto EGLSQL para abrirlo en el editor de componentes de construcción de EGL. Este archivo se encuentra en EGLSQL/EGLSource/EGLSQL.eglbld.
    2. En el editor de componentes de construcción, quite la marca del recuadro de selección Mostrar sólo opciones especificadas.
    3. Bajo Opción, busque sqlCommitControl.
    4. Establezca el valor de sqlCommitControl en NOAUTOCOMMIT.
      Nota: Para abrir la opción sqlCommitControl con el fin de editarla, pulse dos veces despacio en la columna Valor situada junto a dicha opción. También puede pulsar tres veces rápidamente en la columna Valor.
    5. Guarde y cierre el descriptor de construcción.
  2. Abra el programa exceptionHandlingTest que ha creado en la sección anterior.
  3. Añada un registro de pedido al registro de cliente que ya se encuentra en el programa:
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
        
        customerOrder Orders;
        customerOrder.CustomerId = 1;
        customerOrder.OrderId = 50;
        customerOrder.OrderAmount = 0;
        customerOrder.OrderDetails = 
          "Este es mi primer pedido, ¡que salga bien!";
        
        ...
        
      end
  4. Dentro de un bloque try, añada primero el pedido y luego el cliente:
    try
      add customerOrder;
      SysLib.writeStdout("Pedido añadido satisfactoriamente.");
      add customer;
      SysLib.writeStdout("Cliente añadido satisfactoriamente.");
    end
    En este caso, el pedido se añadirá, pero no el cliente, debido a la clave primaria duplicada. Por tanto, puede deshacer el cambio de la tabla ORDERS invocando una retrotracción en el bloque onException.
  5. Añada un bloque onException, incluyendo una retrotracción, después del bloque try:
    try
      add customerOrder;
      SysLib.writeStdout("Pedido añadido satisfactoriamente.");
      add customer;
      SysLib.writeStdout("Cliente añadido satisfactoriamente.");
    onException(ex SQLException)
      SysLib.writeStdout("SQL exception "::ex.messageID);
      SysLib.writeStdout("message = "::ex.message);
      SysLib.writeStdout("Realizando retrotracción.");
      SysLib.rollback();
    end
    Ahora, si alguna sentencia add falla, el bloque onException del código se ejecutará e intentará deshacer los cambios de la base de datos.
  6. Después del bloque onException, añada código para comprobar si la retrotracción ha eliminado correctamente el pedido que se había añadido antes de que se produjera la excepción. Para comprobar si el pedido sigue en la base de datos, puede compararlo con noRecordFound:
    tempOrder Orders;
    tempOrder.OrderId = 50;
    get tempOrder;
    
    if (tempOrder is noRecordFound)
      SysLib.writeStdout("El pedido se ha retrotraído satisfactoriamente.");
    else
      SysLib.writeStdout("El pedido sigue en la base de datos");
    end
    Otra forma de comprobar el estado de una llamada a una base de datos consiste en probar los valores de la variable del sistema sqlLib.sqlData. Esta variable del sistema es un registro, y sus campos almacenan información acerca del éxito o el fracaso de la última operación de base de datos SQL. Concretamente, el campo sqlLib.sqlData.sqlcode contiene 0 si la operación se ha realizado correctamente y ha devuelto resultados, y contiene 100 si la operación se ha realizado correctamente pero la sentencia SELECT no ha devuelto resultados. Por tanto, también podría averiguar si la fila se ha encontrado o no con el código siguiente:
    if (sqlLib.sqlData.sqlcode == 0)
    SysLib.writeStdout("El pedido sigue en la base de datos");
    else
    	if (sqlLib.sqlData.sqlcode == 100)
    SysLib.writeStdout("El pedido se ha retrotraído satisfactoriamente.");
    	end
    end
  7. Genere y ejecute el programa.
Resultados
La salida del programa muestra que el pedido se ha añadido a la base de datos, se ha producido la excepción y el pedido se ha eliminado correctamente como resultado de la retrotracción:
Pedido añadido satisfactoriamente.
SQL exception EGL0504E
message = EGL0504E ADD: La sentencia ha terminado anormalmente
  porque habría provocado un valor de clave duplicado
  en una restricción de clave exclusiva o primaria
  o en un índice exclusivo identificado por 'SQL070307095259210'
  definido en 'CUSTOMER'.[sqlstate:23505][sqlcode:20000]
EGL0002I El error se ha producido en el programa exceptionHandlingTest
  que procesa la función main.
Se realiza la retrotracción.
El pedido se ha retrotraído satisfactoriamente. 
Este es el código completo del archivo exceptionHandlingTest.egl. Si ve errores marcados con símbolos X rojos en el archivo, asegúrese de que el código coincide con el código de este archivo: Archivo exceptionHandlingTest.egl completado después de la lección 7.

Punto de comprobación de lección

En esta lección ha aprendido los conceptos básicos del manejo de errores en las operaciones SQL. Es muy importante anticiparse a los problemas y suministrar manejo de excepciones para permitir la recuperación de la aplicación.
Para obtener más información acerca de los errores de tiempo de ejecución que pueden provocar excepciones, consulte la sección Códigos de error del entorno de ejecución Java de EGL. Para obtener una lista de los registros de excepción que EGL puede crear, consulte la sección Registros de excepción del núcleo de EGL.
< Anterior | Siguiente >

Comentarios