Transacciones con SQLite

Un saludo a todos los lectores de mi blog. En esta oportunidad quiero contarles acerca del manejo de transacciones en SQLite, pues aunque no lo parezca, este pequeño motor de base de datos sí cuenta con mecanismos para ejecutar los conocidos rollback y commit. Hace algún tiempo publiqué una entrada (que les recomiendo leer) en donde mostraba la manera de cómo trabajar con este motor sin embargo no comenté acerca del manejo de transacciones, tema que es de vital importancia pues aseguramos la atomicidad de nuestras operaciones sobre la base de datos y también reducimos el tiempo de ejecución de nuestras consultas.

Para poder entender mejor les voy a mostrar el siguiente ejemplo. Tenemos una pequeña aplicación la cual se encarga de grabar registros de productos en una base de datos. El precio de cada producto es generado por un método que para este caso le llamaremos doStuff y que recibe dos parámetros.
public static double doStuff(int precioTotal, int valor) {
    double precioUnitario = precioTotal / valor;
    return precioUnitario;
}
Como vemos, el método anterior es sensible a generar excepciones, por ejemplo la conocida ArithmeticException. Ahora supongamos que tenemos una lista de productos que queremos insertar en nuestra base de datos tal como se ve en la siguiente sección de código:
Producto producto1 = new Producto();
producto1.setNombre("Ariel");
producto1.setStock(50);
// --
Producto producto2 = new Producto();
producto2.setNombre("Pinol");
producto2.setStock(25);
// --
Producto producto3 = new Producto();
producto3.setNombre("Nivea");
producto3.setStock(50);

// --
Producto producto4 = new Producto();
producto4.setNombre("Colgate");
producto4.setStock(10);
// --
List<Producto> listaProductos = new ArrayList<Producto>();
listaProductos.add(producto1);
listaProductos.add(producto2);
listaProductos.add(producto3);
listaProductos.add(producto4);
Ahora vamos a recorrer nuestra lista de la siguiente manera:
for (Producto producto : listaProductos) {
    try {
        if (producto.getNombre().compareTo("Nivea") == 0) {
            // (1)Aquí vamos a generar un error a propósito
     producto.setPrecioUnitario(doStuff(0, 0));
 } else {
     producto.setPrecioUnitario(doStuff(1, 1));
 }
 //(2) Crear producto en la base de datos
    } catch (Exception e) {
        error = true;
    }
}
En el punto (2) vamos a crear el producto en la base de datos y no vamos a tener ningún problema hasta llegar al producto Nivea ya que en este objeto, al enviar como parámetros 0 y 0 se lanzará la excepción java.lang.ArithmeticException: / by zero la cual impedirá que se inserte en la base de datos. Sin embargo el problema real radica en el hecho de que ya hemos insertado antes dos productos y si vemos la inserción de los cuatro productos como una sola transacción, una operación atómica, entonces debemos eliminar los productos Ariel y Pinol de nuestra base de datos. Al no implementar un mecanismo de rollback y ejecutar el código anterior tendremos en nuestra base de datos lo siguiente:

Como vemos esto realmente no estaría bien pues si fueran datos que dependieran entre ellos, esto generaría datos inconsistentes en nuestra base de datos. Para esto es que implementamos el manejo de transacciones mediante una clase a la que llamaremos SqlProductoFactory y que se encargará de crear las conexiones, cerrarlas, comitearlas o hacer rollback de ser necesario.
package com.blogspot.rolandopalermo.sql;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

import com.blogspot.rolandopalermo.bean.Producto;
import com.blogspot.rolandopalermo.util.ConexionJDBC;

public class SqlProductoFactory {

 private ConexionJDBC conn;
 private Connection connection;
 private Statement stmt;

 public void crearConexion() {
  System.out.println("Creamos la conexión");
  try {
   conn = new ConexionJDBC();
   connection = conn.getConnection();
   connection.setAutoCommit(false);
   stmt = connection.createStatement();
  } catch (SQLException e) {
   System.err.println("Error: " + e.getMessage());
  }
 }

 public void rollback() {
  System.err.println("Hacemos rollback");
  try {
   if (connection != null) {
    connection.rollback();
   }
  } catch (SQLException ex) {
   System.err.println("Error: " + ex.getMessage());
  }
 }

 public void commit() {
  System.out.println("Hacemos commit");
  try {
   if (connection != null && conn != null) {
    connection.commit();
    connection.setAutoCommit(true);
   }
  } catch (SQLException ex) {
   System.err.println("Error: " + ex.getMessage());
  }
 }

 public void close() {
  System.out.println("Cerramos la conexión");
  if (stmt != null) {
   try {
    stmt.close();
   } catch (Exception e) {
    System.err.println("Error: " + e.getMessage());
   }
  }
  if (conn != null) {
   try {
    conn.close();
   } catch (Exception e) {
    System.err.println("Error: " + e.getMessage());
   }
  }
 }

 public void crearProducto(Producto producto) throws SQLException {
  System.out.println("Creamos el producto");
  String sql = "INSERT INTO TB_PRODUCTOS (txtNombre, txtPrecioUnitario, txtStock) VALUES ('"
    + producto.getNombre()
    + "',"
    + producto.getPrecioUnitario()
    + "," + producto.getStock() + ");";
  stmt.execute(sql);
 }
}
Ahora utilizando nuestra clase manejadora de transacciones tendremos algo como esto:
for (Producto producto : listaProductos) {
    try {
        if (producto.getNombre().compareTo("Nivea") == 0) {
            // Aquí vamos a generar un error a propósito
            producto.setPrecioUnitario(doStuff(0, 0));
        } else {
            producto.setPrecioUnitario(doStuff(1, 1));
        }
        sql.crearProducto(producto);
    } catch (Exception e) {
        error = true;
        sql.rollback();
        break;
    }
}
if (!error) {
    sql.commit();
}
sql.close();
Y luego de volver a ejecutar nuestro método de inserción de nuestra lista de productos nuestra base de datos quedará así:


Si ocurre un problema al insertar un producto entonces no se insertará ninguno. De todos modos les adjunto el proyecto que ha sido hecho en Eclipse y no se olviden de colocar la base de datos en el disco D: para poder ejecutar la aplicación.
La base de datos pueden descargarla del siguiente enlace: base de datos. Un saludo a todos y hasta una próxima oportunidad.

Comentarios