Cómo usar una base de datos SQLite en Android

En Android es posible utilizar diferentes formas para almacenar la información, una de ellas es el uso de base de datos. Android viene con un soporte integrado para el manejo de bases de datos SQLite, que permite almacenar información estructurada de forma sencilla. En este artículo muestro un ejemplo sencillo de utilización de una base de datos en Android para gestionar un conjunto de libros.

Antes de entrar en harina con la base de datos, vamos a definir la clase Libro, que consta de autor, título, resumen, editorial y año de publicación de la edición. El siguiente código Java representa esta clase.

public class Libro {
   private long id = 0;
   private String autor = "";
   private String titulo = "";
   private String resumen = "";
   private String editorial = "";
   private int anyo = 0;
 
   public Libro(String autor, String titulo, String resumen, String editorial, int anyo) {
      this.autor = autor;
      this.titulo = titulo;
      this.resumen = resumen;
      this.editorial = editorial;
      this.anyo = anyo;
   }
 
   public Libro(long id, String autor, String titulo, String resumen, String editorial, int anyo) {
      this(autor, titulo, resumen, editorial, anyo);
      this.id = id;
   }
 
   public String getAutor() {
      return autor;
   }
 
   public void setAutor(String autor) {
      this.autor = autor;
   }
 
   public String getTitulo() {
      return titulo;
   }
 
   public void setTitulo(String titulo) {
      this.titulo = titulo;
   }
 
   public String getResumen() {
      return resumen;
   }
 
   public void setResumen(String resumen) {
      this.resumen = resumen;
   }
 
   public String getEditorial() {
      return editorial;
   }
 
   public void setEditorial(String editorial) {
      this.editorial = editorial;
   }
 
   public int getAnyo() {
      return anyo;
   }
 
   public void setAnyo(int anyo) {
      this.anyo = anyo;
   }
 
   public long getId() {
      return id;
   }
}

Una vez definida la clase, necesitamos disponer de un medio de acceso a la base de datos para almacenar la información y poder recuperarla y actualizarla. Para ello, la manera de trabajar habitual en Android se basa en la creación de una clase que centraliza el acceso a la base de datos. En este caso vamos a crear una clase de nombre DBHelper. Al final del post indico el texto completo de la clase, pero antes veamos punto por punto su funcionamiento.

El siguiente fragmento de código muestra parte de la definición de la clase DBHelper. Las 4 variables estáticas representan información básica de la clase. Así, DATABASE_VERSION representa la versión de la base de datos que utiliza la aplicación.

public class DBHelper {
 
   private static final String TAG = "DBHelper";
 
   private static final int DATABASE_VERSION = 1;
   private static final String DATABASE_NAME = "books.db";
   private static final String BOOKS_TABLE_NAME = "books";
 
   public static final class Libros implements BaseColumns {
      private Libros() {}
      public static final String AUTOR = "author";
      public static final String TITULO = "title";
      public static final String RESUMEN = "summary";
      public static final String EDITORIAL = "publisher";
      public static final String ANYO = "year";
   }
   .......
   .......
}

Cada vez que se inicia la aplicación se comprueba si la base de datos instalada es la misma que la indicada en el código, y si no lo es, se llama a un método que permite actualizarla. De esta forma, con esta variable es posible actualizar la estructura de la base de datos entre cambios de versión de la aplicación. La variable DATABASE_NAME indica el nombre del archivo que contendrá la base de datos en el sistema de archivos, en este caso “books.db”.

Con el fin de independizar lo más posible de la estructura interna de las tablas, se define la variable BOOKS_TABLE_NAME, que indica el nombre de la tabla en la que se van a almacenar los libros (“books”), y se define la clase interna Libros, que implenta el interfaz BaseColumns, y que consta de un conjunto de variables estáticas representando cada uno de los campos de la tabla. Para cada uno de los campos se indica el nombre que se le dará en la tabla. De esta forma, cuando en el código queramos referirnos a la columna titulo de la tabla libros, podremos utilizar Libros.TITULO, en vez de utilizar el string "title"“. Hay que tener en cuenta que no es necesario definir el campo de identificador, ya que se dispone automáticamente del campo _ID.

Cuando se crea una instancia de la clase DBHelper, con la llamada al constructor DBHelper, se guarda el contexto y se crea una instancia (openHelper) de la clase MyDBOpenHelper, encargada de proporcionar el acceso a la base de datos SQLite. Cuando se llama al método open se obtiene acceso de escritura a la base de datos a través de openHelper. La clase MyDBOpenHelper es una clase interna, que extiende SQLiteOpenHelper, y que permite crear la base de datos y su estructura de tablas, así como actualizar esta estructura cuando se incrementa el número de versión.

public class DBHelper {
   .......
   .......
   private Context context;
   private SQLiteDatabase db;
   private MyDBOpenHelper openHelper;
 
   public DBHelper(Context context) {
      this.context = context;
      this.openHelper = new MyDBOpenHelper(this.context);
   }
 
   public DBHelper open(){
      this.db = openHelper.getWritableDatabase();      
      return this;
   }
 
   public void close() {
      this.db.close();
   }
 
   private static class MyDBOpenHelper extends SQLiteOpenHelper {
 
      MyDBOpenHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }
 
      @Override
      public void onCreate(SQLiteDatabase db) {
          db.execSQL("CREATE TABLE " + BOOKS_TABLE_NAME + " ("
             + Libros._ID + " INTEGER PRIMARY KEY,"
             + Libros.AUTOR + " TEXT,"
             + Libros.TITULO + " TEXT,"
             + Libros.RESUMEN + " TEXT,"
             + Libros.EDITORIAL + " TEXT,"
             + Libros.ANYO + " INTEGER"
             + ");");
         }
 
      @Override
      public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
          Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
             + newVersion + ", which will destroy all old data");
          db.execSQL("DROP TABLE IF EXISTS "+BOOKS_TABLE_NAME);
          onCreate(db);
      }
   }
   .......
   .......
}

Como se aprecia en el fragmento de código anterior, la clase MyDBOpenHelper dispone de dos métodos llamados onCreate y onUpgrade, que son llamados de forma automática al crear la base de datos y al actualizarla, cuando se detecta un incremento en el número de versión. En este caso, en la creación se crea la tabla de libros y en la actualización se borra la tabla y se vuelve a crear, sin embargo, es posible definir comportamientos más complejos, definiendo más tablas, índices, relaciones entre tablas, etc., así como estrategias de actualización que no impliquen el borrado de toda la información previa.

En este punto ya tenemos definida la información básica de la base de datos, con sus tablas y los campos de cada tabla, así como los mecanismos necesarios para crear la base de datos, actualizar su estructura y conectarnos a ella. Queda por tanto definir la lógica necesaria para acceder a la información.

Aunque es posible utilizar consultas SQL indicadas de forma explícita, y también es posible utilizar prepared statements, la forma habitual de realizar consultas y actualizaciones, es utilizar los métodos query, insert, update y delete, de los que se dispone en la clase SQLiteDatabase. También es posible construir consultas más complejas, que involucren varias tablas por ejemplo, mediante la utilización de SQLiteQueryBuilder.

Así, para obtener registros de la tabla se utiliza el método query. El siguiente fragmento de código muestra como recuperar un registro de un libro concreto a partir de su identificador. Se le indica la tabla y la condición que deben cumplir los registros devueltos. Esta condición se indica en dos partes, indicando primero la condición con el uso de caracteres “?”, que serán sustituidos por los valores proporcionados en un array de strings. En este caso, se indica una condición que equivale a _ID=?, donde se sustituye el carácter “?” por el identificador proporcionado. Una vez obtenido el registro se crea una instancia de la clase libro y se devuelve. Hay que tener en cuenta que por simplicidad se ha suprimido toda gestión de errores.

public class DBHelper {
   .......
   .......
   public Libro selectBook(long id) {
      Libro libro = null;
      Cursor cursor = db.query(BOOKS_TABLE_NAME, 
            null, Libros._ID+"=?", new String[] {Long.toString(id)}, 
            null, null, null);
      cursor.moveToFirst();
      libro = new Libro(cursor.getLong(0), cursor.getString(1),
            cursor.getString(2), cursor.getString(3), 
            cursor.getString(4), cursor.getInt(5));
      return libro;
   }   
   .......
   .......
}

A continuación se muestra como recuperar todos los libros existentes en un ArrayList ordenados por su título. A diferencia del caso anterior, no se indica ningún parámetro de búsqueda y se indica mediante el último parámetro el criterio de ordenación.

public class DBHelper {
   .......
   .......
   public ArrayList<Libro> selectAllBooks() {
      ArrayList<Libro> list = new ArrayList<Libro>();
      Cursor cursor = this.db.query(BOOKS_TABLE_NAME, 
            null, null, null, null, null, Libros.TITULO+" ASC");
      if (cursor.moveToFirst()) {
         do {
            Libro libro = new Libro(cursor.getLong(0), cursor.getString(1),
                  cursor.getString(2), cursor.getString(3), 
                  cursor.getString(4), cursor.getInt(5));
 
            list.add(libro);
         } while (cursor.moveToNext());
      }
      if (cursor != null && !cursor.isClosed()) {
         cursor.close();
      }
      return list;
   }
   .......
   .......   
}

Los siguientes métodos permiten realizar la inserción, actualización y borrado de registros. El funcionamiento es similar al de el método query. Se le indica a cada método la tabla sobre la que trabajar, una condición si se trata de una actualización o borrado de registros, y el conjunto de valores a insertar o actualizar. Estos valores se proporcionan mediante una instancia de ContentValues que contiene para cada campo de la tabla su valor.

public class DBHelper {
   .......
   .......
   public long insertBook(Libro libro) {
      ContentValues values = new ContentValues();
      values.put(Libros.AUTOR, libro.getAutor());        
      values.put(Libros.TITULO, libro.getTitulo());
      values.put(Libros.RESUMEN, libro.getResumen());
      values.put(Libros.EDITORIAL, libro.getEditorial());
      values.put(Libros.ANYO, libro.getAnyo());
      long id = db.insert(BOOKS_TABLE_NAME, null, values);
      return id;
   }
 
   public void updateBook(Libro libro) {
      ContentValues values = new ContentValues();
      values.put(Libros.AUTOR, libro.getAutor());
      values.put(Libros.TITULO, libro.getTitulo());
      values.put(Libros.RESUMEN, libro.getResumen());
      values.put(Libros.EDITORIAL, libro.getEditorial());
      values.put(Libros.ANYO, libro.getAnyo());
      db.update(BOOKS_TABLE_NAME, values, Libros._ID+"=?", new String[] {Long.toString(libro.getId())});
   }
 
   public void deleteBook(Libro libro){
      db.delete(BOOKS_TABLE_NAME, Libros._ID+"=?", new String[] {Long.toString(libro.getId())});
   }
   .......
   .......   
}

Llegados a este punto ya disponemos de toda la lógica necesaria para gestionar la base de datos de libros. El código resultante es el siguiente.

public class DBHelper {
 
   private static final String TAG = "DBHelper";
 
   private static final int DATABASE_VERSION = 1;
   private static final String DATABASE_NAME = "books.db";
   private static final String BOOKS_TABLE_NAME = "books";
 
   private Context context;
   private SQLiteDatabase db;
   private MyDBOpenHelper openHelper;
 
   public DBHelper(Context context) {
      this.context = context;
      this.openHelper = new MyDBOpenHelper(this.context);
   }
 
   public DBHelper open(){
      this.db = openHelper.getWritableDatabase();      
      return this;
   }
 
   public void close() {
      this.db.close();
   }
 
   public static final class Libros implements BaseColumns {
      private Libros() {}
      public static final String AUTOR = "author";
      public static final String TITULO = "title";
      public static final String RESUMEN = "summary";
      public static final String EDITORIAL = "publisher";
      public static final String ANYO = "year";
   }
 
   private static class MyDBOpenHelper extends SQLiteOpenHelper {
 
      MyDBOpenHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
      }
 
      @Override
      public void onCreate(SQLiteDatabase db) {
          db.execSQL("CREATE TABLE " + BOOKS_TABLE_NAME + " ("
             + Libros._ID + " INTEGER PRIMARY KEY,"
             + Libros.AUTOR + " TEXT,"
             + Libros.TITULO + " TEXT,"
             + Libros.RESUMEN + " TEXT,"
             + Libros.EDITORIAL + " TEXT,"
             + Libros.ANYO + " INTEGER"
             + ");");
         }
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
          Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
             + newVersion + ", which will destroy all old data");
          db.execSQL("DROP TABLE IF EXISTS "+BOOKS_TABLE_NAME);
          onCreate(db);
      }
   }
 
   public Libro selectBook(long id) {
      Libro libro = null;
      Cursor cursor = db.query(BOOKS_TABLE_NAME, 
            null, Libros._ID+"=?", new String[] {Long.toString(id)}, 
            null, null, null);
      cursor.moveToFirst();
      libro = new Libro(cursor.getLong(0), cursor.getString(1),
            cursor.getString(2), cursor.getString(3), 
            cursor.getString(4), cursor.getInt(5));
      return libro;
   }
 
   public ArrayList<Libro> selectAllBooks() {
      ArrayList<Libro> list = new ArrayList<Libro>();
      Cursor cursor = this.db.query(BOOKS_TABLE_NAME, 
            null, null, null, null, null, Libros.TITULO+" ASC");
      if (cursor.moveToFirst()) {
         do {
            Libro libro = new Libro(cursor.getLong(0), cursor.getString(1),
                  cursor.getString(2), cursor.getString(3), 
                  cursor.getString(4), cursor.getInt(5));
 
            list.add(libro);
         } while (cursor.moveToNext());
      }
      if (cursor != null && !cursor.isClosed()) {
         cursor.close();
      }
      return list;
   }
 
   public long insertBook(Libro libro) {
      ContentValues values = new ContentValues();
      values.put(Libros.AUTOR, libro.getAutor());        
      values.put(Libros.TITULO, libro.getTitulo());
      values.put(Libros.RESUMEN, libro.getResumen());
      values.put(Libros.EDITORIAL, libro.getEditorial());
      values.put(Libros.ANYO, libro.getAnyo());
      long id = db.insert(BOOKS_TABLE_NAME, null, values);
      return id;
   }
 
   public void updateBook(Libro libro) {
      ContentValues values = new ContentValues();
      values.put(Libros.AUTOR, libro.getAutor());
      values.put(Libros.TITULO, libro.getTitulo());
      values.put(Libros.RESUMEN, libro.getResumen());
      values.put(Libros.EDITORIAL, libro.getEditorial());
      values.put(Libros.ANYO, libro.getAnyo());
      db.update(BOOKS_TABLE_NAME, values, Libros._ID+"=?", new String[] {Long.toString(libro.getId())});
   }
 
   public void deleteBook(Libro libro){
      db.delete(BOOKS_TABLE_NAME, Libros._ID+"=?", new String[] {Long.toString(libro.getId())});
   }
 
}

A continuación se muestra un ejemplo con una serie de acciones realizadas sobre la base de datos.

// definición de dos libros
Libro libro1 = new Libro("El Quijote", "Cervantes", "En un lugar de la mancha...", "Planeta", 2010);
Libro libro2 = new Libro("Harry Potter y la piedra filosofal", "J.K. Rowling", "La historia cuenta la historia de Harry Potter, un mago de 11 años ....", "Bloomsbury Publishing", 1997);
 
// creación de la conexión a la base de datos
DBHelper db = new DBHelper(this);
db.open();
 
// insertar los dos libros
db.insertBook(libro1);
db.insertBook(libro2);
 
// obtener una lista con todos los libros
ArrayList<Libro> libros = db.selectAllBooks();
 
// modificar el autor de un libro
libro1 = db.selectBook(1);
libro1.setAutor("Miguel de Cervantes Saavedra");
db.updateBook(libro1);
 
// borrar un libro
db.deleteBook(libro1);
 
// cierre de la conexión a la base de datos
db.close();
Twitter Digg Delicious Stumbleupon Technorati Facebook Email

9 Respuestas para “Cómo usar una base de datos SQLite en Android”

  1. el 30 de noviembre no era día para publicar en internet
    tenías que preparar el doctorado coleguita
    y luego ir con tus amigos a ver el 5-0 del Barça!!!!!!!!!!!!
    partidazo!!!!!!!!!!!!!!!!!

  2. Ay! cómo somos… Que la entrada estaba ya escrita hace días, es lo que se llama publicación diferida 😛 Ah! y por cierto, no era la Tesis, que ya está acabada, tenía que hacer el currículum con las publicaciones para adjuntarlo al depósito.

    Vi el resumen del partido y la verdad es que estuvo bien, el madrid no jugó ni a tabas y acabaron desquiciados.

  3. así me gusta, respuestas razonadas
    buen día, que para mí será la noche ;D

  4. Hola, muy buen tutorial pero lo he seguido y me da error. La cuestión es que necesito que se me muestre por pantalla la lista al apretar un botón que ya tengo creado. También quiero que el usuario pueda agregar libros en la base de datos. Me salta el error: The application has stopped unexpectedly. Please try again.

    Te estaría muy agradecido si me pudieras ayudar.

  5. Es decir, como hago para que me agregue libros y me los muestre por pantalla. He agregado los métodos que has añadido al final pero no me los coge, me da error. Tengo implementada la DBHelper pero quiero que se añadir libros y que se muestren.

    Gracias y un saludo.

  6. buenas e seguido el ejemplo, en eclipse no me muestra ningún tipo de error, pero al ejecutar la aplicación en el emulador de android me aparece un error que dice: “el tiempo de ejecución a acabado repentinamente; forzar cierre”, al principio pensé que era el emulador por lo que instale el apk en mi celular pero me salio el mismo mensaje, no se si debo modificar el AndroidManifest.xml

  7. Que tal amigo tengo una duda en el codigo la clase

    public static final class Libros implements BaseColumns {
    private Libros() {}
    ………..
    ………..
    }

    cual es su funcion o finalidad porque no me queda muy claro.

    Saludos!!

  8. Patxi Echarte 15. Abr, 2013 en 8:01 pm

    Hola Jose, esa clase sirve para definir las columnas que tiene la tabla, de manera que cuando se montan las consultas no se ponga el nombre de cada columna en string, repetido por múltiples sitios, sino que se coja de un sitio centralizado que es esta clase.

  9. Felicidades en buen Tutorial
    pero tengo un problema en el Query
    Cursor cursor = this.db.query(BOOKS_TABLE_NAME,
    null, null, null, null, null, Libros.TITULO+” ASC”);
    me da error de ejecución en TITULO + ASC
    ¿Me podrias decir porque?
    Gracias