Trabajando con BBDD - jpexposito/android GitHub Wiki

¿Por qué tan importante el uso de Bases de Datos en Android?

Antes de comenzar a profundizar en el motor de Base de Datos SQLite, es conveniente aclarar la importancia de usar un almacén de datos para nuestras aplicaciones Android, que nos proporcionarán un nivel extra a la hora de desarrollar Apps para dispositivos móviles.

Si bien es cierto que no todas las aplicaciones requieren de la potencia de una base de datos, debido principalmente a que no necesitan almacenar mucha información, los nuevos desarrollos para aplicaciones empresariales, hacen cada vez más necesaria su implementación.

Los motores de Base de Datos facilitan separar la lógica de negocio del componente visual que nos proporciona la interfaz de usuario, y del código ‘trasero’ que proporciona toda la funcionalidad de la aplicación, permitiéndonos implementar el conocido patrón de diseño MVC (Modelo-Vista-Controlador).

Gran parte de su importancia se debe al ciclo de vida de una aplicación Android, que puede ocasionar el cierre de la aplicación para la liberación de recursos, haciendo evidente la necesidad de un almacén de datos que mantenga la persistencia de los datos ante este tipo de situaciones.

Motor de Base de Datos SQLite

SQLite es una librería software que posibilita la gestión de bases de datos relacionales.

A diferencia de otros gestores de base de datos cliente-servidor, no está implementado de manera independiente al programa con el que establece comunicación, más bien forma parte de él, integrándose en su estructura, formando lo que se denomina un gestor de base de datos embebido o empotrado. Por lo tanto, todas las operaciones de base de datos se manejan dentro de la aplicación mediante llamadas y funciones contenidas en la librería SQLite.

Características más destacadas de SQLite:

  • Escrito en C y rodeado de un envoltorio Java proporcionado por el SDK de Android.
  • Base de Datos de hasta 2 Terabytes de tamaño.
  • Gestión de Base de Datos transaccional, autocontenido, sin servidor ni configuración.
  • Permite campos de tipo BLOB–>Binary Large Object para almacenar archivos binarios grandes como puede ser una imagen.
  • Posibilita las relaciones entre tablas.
  • Es posible trabajar con Bases de Datos virtuales en memoria, sin archivo físico.
  • Software libre cuyo código fuente es de dominio público y licencia GPL.

Desventajas

La utilización de una base de datos SQLite en un app, es un poco complicada; si se ve desde el punto de vista de que amerita una gran cantidad de procesos de codificación para poder organizar las tablas en la que serán mostrados los datos.

También complica un poco la utilización de la aplicación; debido a que, si la base de datos no está en la nube, sino, que es parte de la aplicación, esto puede conllevar a un aumento del espacio necesitado para su utilización.

Interactuar con la BBDD

En este punto y teniendo en cuenta los datos que debemos de gestionar, debemos identificar las operaciones que vamos a realizar sobre la BBDD. Como todo sistema de datos, las operaciones básicas a realizar son, en el 99% un CRUD:

Cada una de estas acciones llevan asociadas un tipo de operación en la cualquier sistema de almacenamiento que se este utilizando.

Acción Método
Create insert
Read get
Update update
Delete delete

Patrón de package en la BBDD

En primer lugar vamos a definir el package para trabajar en la BBDD.

  • database. Será el paquete donde se define la interacción con la BBDD.
    • contract. Paquete donde se definen los contratos. La clase de contratos es un contenedor de constantes que definen nombres de URI, tablas y columnas. Esta clase te permite utilizar las mismas constantes en todas las otras clases del mismo paquete, por lo que puedes cambiar el nombre de una columna en un lugar y propagar ese cambio en todo el código. Una forma adecuada de organizar una clase de contratos consiste en incluir definiciones que sean globales para toda la base de datos en el nivel raíz de la clase. Luego, se debe crear una clase interna para cada tabla. Cada clase interna enumera las columnas de tabla correspondientes.
    • helper. Contiene un conjunto útil de API para administrar tu base de datos. Cuando utilizas esta clase para obtener referencias a tu base de datos, el sistema realiza las operaciones de larga duración para crear y actualizar la base de datos solo cuando es necesario y no durante el inicio de la app.

Veamos un ejemplo: Por ejemplo, en el siguiente contrato, se define el nombre de la tabla y los nombres de columna de una sola tabla que representa un feed RSS:

public final class FeedReaderContract {
  // To prevent someone from accidentally instantiating the contract class,
  // make the constructor private.
  private FeedReaderContract() {}

  /* Inner class that defines the table contents */
  public static class FeedEntry implements BaseColumns {
      public static final String TABLE_NAME = "entry";
      public static final String COLUMN_NAME_TITLE = "title";
      public static final String COLUMN_NAME_SUBTITLE = "subtitle";
  }
}

Donde hemos de definir la sentencia para la creación y eliminación de la tabla de la BBDD:

private static final String SQL_CREATE_ENTRIES =
  "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
  FeedEntry._ID + " INTEGER PRIMARY KEY," +
  FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
  FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";

  private static final String SQL_DELETE_ENTRIES =
  "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

Por último realizamos la creación del helper. Recuerda que los helper en Android heredan de SQLiteOpenHelper contiene un conjunto útil de API para administrar tu base de datos.

public class FeedReaderDbHelper extends SQLiteOpenHelper {
  // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

Dedica un momento a pensar como dejar este código optimizado para la creación no sea de una única tabla si no de varias en la BBDD.

En este caso se debe de realizar un ComunHelper del que hereden todos para evitar duplicar código.

  public class ComunHelper extends SQLiteOpenHelper {
    ...
  }

  public class FeedReaderDbHelper extends ComunHelper {
    ...
  }
   

Para instanciar la clase será de forma similar a:

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Todas estas clases dentro del paquete adecuado.

Operaciones:

Insertar

Inserta datos en la base de datos pasando un objeto ContentValues al método insert():

// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);

// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

Obtener

Para leer desde una base de datos, usa el método query(), pasando los criterios de selección y columnas deseadas. El método combina elementos de insert() y update(), excepto que la lista de columnas define los datos que deseas recuperar (la "proyección") en lugar de datos para insertar. Los resultados de la consulta se muestran en un objeto Cursor.

SQLiteDatabase db = dbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
  BaseColumns._ID,
  FeedEntry.COLUMN_NAME_TITLE,
  FeedEntry.COLUMN_NAME_SUBTITLE
  };

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };

// How you want the results sorted in the resulting Cursor
String sortOrder =
  FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
  FeedEntry.TABLE_NAME,   // The table to query
  projection,             // The array of columns to return (pass null to get all)
  selection,              // The columns for the WHERE clause
  selectionArgs,          // The values for the WHERE clause
  null,                   // don't group the rows
  null,                   // don't filter by row groups
  sortOrder               // The sort order
  );

Finalmente recorremos el cursor:

List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
  long itemId = cursor.getLong(
      cursor.getColumnIndexOrThrow(FeedEntry._ID));
  itemIds.add(itemId);
}
cursor.close();

Modificar

Cuando debas modificar un subconjunto de los valores de la base de datos, usa el método update().

La actualización de la tabla combina la sintaxis ContentValues de insert() con la sintaxis WHERE de delete().

SQLiteDatabase db = dbHelper.getWritableDatabase();

// New value for one column
String title = "MyNewTitle";
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the title
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
String[] selectionArgs = { "MyOldTitle" };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);

Eliminar

Para borrar filas de una tabla, debes proporcionar criterios de selección que identifiquen las filas para el método delete(). El mecanismo funciona igual que los argumentos de selección del método query().

// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { "MyTitle" };
// Issue SQL statement.
int deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);

Recuerda que este código es muy optimizable, y se debe de realizar para que su mantenimiento sea lo más simple posible.

Conectar a la BBDD

Es costoso cuando la base de datos está cerrada, debes dejar abierta la conexión con la base de datos durante el tiempo que posiblemente necesites acceder a ella.

Desconectar a la BBDD

Por lo general, lo óptimo es cerrar la base de datos en el método onDestroy() de la actividad de llamada.

@Override
protected void onDestroy() {
    dbHelper.close();
    super.onDestroy();
}

Referencias

Trabajar con BBDD en Android.

⚠️ **GitHub.com Fallback** ⚠️