«

»

dic 04 2012

Primeros pasos con ORMLite

 

 

En este blog ya os hemos hablado de cómo facilitar el trabajo con bases de datos SQLite. En concreto hemos visto cómo trabajar con Android DataFramework y ADA Framework. Hoy os traemos una pequeña joya, una librería que en mis proyectos se ha convertido en una fija, ya no sólo por su facilidad de uso sino también por su potencia. Os estoy hablando de ORMLite.
 

ORMLite es de esas librerías que al principio pueden resultar poco vistosas con una página poco cuidada y una documentación difícil de leer. Pero cuando trabajas con ella descubres lo fiable que es y la cantidad de posibilidades que ofrece. Sobra decir que ORMLite es una librería ORM clásica que nos permite mapear clases Java con tablas en la base de datos.

En esta entrada veremos qué nos hace falta en nuestro proyecto para empezar a utilizar la librería, cómo modelar las clases, cómo crear nuestro helper y por último algunos ejemplos sencillos.

 

Elementos necesarios

Lo primero que tenemos que tener es un proyecto android. ORMLite se distribuye a partir de dos librerías JAR que debemos incluir en el directorio libs de nuestro proyecto:

ormlite-core: Núcleo de la librería
ormlite-android: Clases específicas de android

Debemos incluir ambas en nuestro proyecto y para descargarlas tenemos varias opciones:

 

Modelar las clases

El siguiente paso es elegir las clases del modelo que queremos mapear a tablas en nuestra base de datos. Cada clase será una tabla en nuestra base de datos y lo indicaremos a través de anotaciones sobre la clase. Lo mejor es verlo a través de un ejemplo, para ello supongamos que tenemos dos clases, Usuario y Grupo, que representan a usuarios y grupos respectivamente. Un usuario sólo puede estar en un grupo y el grupo puede tener más de un usuario.

La clase Usuario tendría la siguiente forma:
 

package es.androcode.androcode_ormlite;

import java.util.Date;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable
public class Usuario {

    public static final String ID = "_id";
    public static final String NOMBRE = "nombre";
    public static final String FECHA_NACIMIENTO = "fecha_nacimiento";
    public static final String GRUPO = "grupo";

    @DatabaseField(generatedId = true, columnName = ID)
    private int id;
    @DatabaseField(columnName = NOMBRE)
    private String nombre;
    @DatabaseField(columnName = FECHA_NACIMIENTO)
    private Date fechaNacimiento;    
    @DatabaseField(foreign = true, columnName = GRUPO)
    private Grupo grupo;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

    public Date getFechaNacimiento() {
        return fechaNacimiento;
    }

    public void setFechaNacimiento(Date fechaNacimiento) {
        this.fechaNacimiento = fechaNacimiento;
    }

    public Grupo getGrupo() {
        return grupo;
    }

    public void setGrupo(Grupo grupo) {
        this.grupo = grupo;
    }

}

 
Vamos a ir desgranando las principales partes importantes de este código.

 
Línea 8
Anotación DatabaseTable. Mediante esta anotación estamos indicando que esta clase tendrá su correspondiente tabla en la base de datos. Esta anotación acepta el atributo tableName que permite indicar un nombre concreto de la tabla.

 
Líneas 11 a 14
Constantes para especificar los nombres de las columnas. Esta es una práctica que suelo utilizar a menudo y que recomiendo encarecidamente. Esto suele resultar muy útil porque más adelante necesitaremos el nombre de la columna para ordenar o realizar consultas y no es aconsejable poner las cadenas en cada uso.

 
Línea 16 – DatabaseField(generatedId = true, columnName = ID)
Esta anotación se sitúa sobre el campo que hace de clave primaria. El campo puede ser de cualquier tipo, pero se aconseja que sea de tipo int o tipo long. Hemos usado generatedId para indicar que el id se genere automáticamente al crear objetos. Podríamos haber puesto generateIdSequence para que se autogenere mediante secuencia de base de datos o id, si quisiéramos indicar nosotros el id. El segundo parámetro es columnName que indica el nombre de la columna. Otra recomendación es utilizar como nombre de columna para el id “_id”, ya que es lo recomendado por android y nos facilitará compatibilidades con los CursorAdapter por ejemplo.

 
Líneas 18 a 21
Definición del resto de propiedades y columnas

 
Línea 22 – @DatabaseField(foreign = true, columnName = GRUPO)
Definimos la relación con la entidad Grupo, indicado mediante el atributo foreign. A través de este valor, estamos diciéndole al motor de ORMLite que la propiedad grupo hace referencia a otra entidad con su correspondiente id.

La entidad Grupo es muy simple, ya que para este ejemplo no vamos a guardar relación con Usuario.

 

package es.androcode.androcode_ormlite;

import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;

@DatabaseTable
public class Grupo {

    public static final String ID = "_id";
    public static final String NOMBRE = "nombre";

    @DatabaseField(generatedId = true, columnName = ID)
    private int id;
    @DatabaseField(columnName = NOMBRE)
    private String nombre;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getNombre() {
        return nombre;
    }

    public void setNombre(String nombre) {
        this.nombre = nombre;
    }

}

 

Creando el helper

Siempre que queramos trabajar con SQLite en Android tendremos que crear un helper. Una clase helper se encarga de crear, actualizar y proporcionar acceso a la base de datos. En ORMLite tendremos que extender de OrmLiteSqliteOpenHelper. También le añadiremos el acceso a los DAO para que podamos utilizarlos desde otras partes del código.

Vamos a ver cómo quedaría nuestra clase helper y luego explicaremos las partes más importantes.
 

package es.androcode.androcode_ormlite;

import java.sql.SQLException;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

public class DBHelper extends OrmLiteSqliteOpenHelper {

    private static final String DATABASE_NAME = "androcode_ormlite.db";
    private static final int DATABASE_VERSION = 1;

    private Dao<Usuario, Integer> usuarioDao;
    private Dao<Grupo, Integer> grupoDao;

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
        try {
            TableUtils.createTable(connectionSource, Usuario.class);
            TableUtils.createTable(connectionSource, Grupo.class);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        onCreate(db, connectionSource);
    }

    public Dao<Usuario, Integer> getUsuarioDao() throws SQLException {
        if (usuarioDao == null) {
            usuarioDao = getDao(Usuario.class);
        }
        return usuarioDao;
    }

    public Dao<Grupo, Integer> getGrupoDao() throws SQLException {
        if (grupoDao == null) {
            grupoDao = getDao(Grupo.class);
        }
        return grupoDao;
    }

    @Override
    public void close() {
        super.close();
        usuarioDao = null;
        grupoDao = null;
    }

}

 
Líneas 18 y 19
Declaramos los DAO. A través de estos objetos realizaremos todas las operaciones de la base de datos. Cada objeto del modelo tiene su propio DAO asociado.

 
Líneas 21 a 23
El constructor recibe la versión de la base de datos y el nombre de la base de datos. Esta parte es común al resto de helpers. Existen formas de acelerar el proceso de creación de las tablas, pero es algo que veremos más adelante.

 
Líneas 25 a 33
Es el método onCreate que se encarga de crear las tablas. En este caso hacemos del método TableUtils.createTable de la API de ORMLite. Recibe como parámetro la conexión y la clase del modelo para la que crear la tabla. Como vemos esto resulta muy cómodo, mucho más si lo comparamos con tener que crear las tablas a mano.

 
Líneas 35 a 38
Método onUpgrade. En este caso al ser la primera versión de la base de datos no necesitamos actualizarla. Aquí tendríamos que poner el código encargado de actualizar la base de datos de una versión a otra tal y como lo hacemos con los helpers de android.

 
Líneas 40 a 52
Son los métodos a través de los que podemos recuperar los DAO. Estos métodos crearán el DAO si no está inicializado o lo devolverán si ya está creado.

 
Línea 54 a 59
Método close que se encarga de liberar los recursos.

 

Ejemplo de uso

Lo primero que tenemos que plantearnos es, ¿cómo recupero el helper?. Los helpers suelen estar en el ámbito de las actividades, de forma que los fragments pueden tener acceso a través de la actividad que lo ha creado. También pueden utilizarse en servicios u otros elementos.

La forma de crear el helper es la siguiente:
 

DBHelper helper = OpenHelperManager.getHelper(context, DBHelper.class);

 
Llamamos al método OpenHelperManager.getHelper pasándole como argumentos el contexto y la clase antes definida.

Es importante liberar recursos una vez que no vayamos a hacer operaciones con la base de datos. En el caso de las actividades podemos realizar esto en el método onDestroy. En las actividades en las que vayamos a necesitar acceso al helper suelo utilizar el siguiente trozo de código (puedes moverlo a una clase abstracta y extender de ella si tienes más de una actividad con acceso a base de datos).
 

private DBHelper mDBHelper;

    private DBHelper getHelper() {
        if (mDBHelper == null) {
            mDBHelper = OpenHelperManager.getHelper(this, DBHelper.class);
        }
        return mDBHelper;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mDBHelper != null) {
            OpenHelperManager.releaseHelper();
            mDBHelper = null;
        }
    }

 
Resumiendo, creamos una propiedad para almacenar la referencia a DBHelper y nos creamos un método de acceso para inicializar la clase sólo cuando haga falta. Observa que si nunca accedemos a dicho método, nunca inicializaremos la clase DBHelper. Por último se añade al método onDestroy la liberación de recursos.

Ahora vamos a ver algunos ejemplos sencillos de cómo interactuar con la base de datos. Como veremos a continuación, los métodos de los DAO pueden lanzar SQLException. ORMLite siempre nos ofrece mucha flexibilidad y en este caso no iba a ser menos. Si nos sentimos incómodos capturando en cada trozo de código en el que hagamos operaciones con base de datos simplemente cambiamos el uso de la clase Dao por RuntimeExceptionDao. Las excepciones que lanza RuntimeExceptionDao son de tipo runtime, por lo que no es necesario capturarlas. De todas formas no os aconsejo esto último, porque si falla una operación en la base de datos tendréis que actuar en consecuencia y no simplemente dejar que la aplicación de un force close al usuario.

 
Crear un objeto
 

Dao dao;
try {
    dao = getHelper().getUsuarioDao();
    Usuario usuario = new Usuario();
    usuario.setFechaNacimiento(new Date());
    usuario.setNombre("Fede");
    dao.create(usuario);
} catch (SQLException e) {
    Log.e(TAG, "Error creando usuario");
}

 
Recuperar objetos
 

Dao dao;
try {
    dao = getHelper().getUsuarioDao();
    Usuario usuario = dao.queryForId(1);
    if (usuario == null) {
        Log.d(TAG, "Ningún usuario con id = 1");                
    } else {
        Log.d(TAG, "Recuperado usuario con id = 1: " + usuario.getNombre());
    }
    List usuarios = dao.queryForEq(Usuario.NOMBRE, "Fede");
    if (usuarios.isEmpty()) {
        Log.d(TAG, "No se encontraron usuarios con nombre = Fede");                
    } else {
        Log.d(TAG, "Recuperado usuarios con nombre = Fede " + usuarios);                
    }
} catch (SQLException e) {
    Log.e(TAG, "Error creando usuario");
}

 
El objeto DAO permite realizar consultas simples. Para consultas más complejas tendremos utilizar un QueryBuilder. A continuación podemos ver un ejemplo sencillo. En una próxima entrada veremos usos más avanzados de consultas.
 

Dao dao;
try {
    dao = getHelper().getUsuarioDao();
    QueryBuilder queryBuilder = dao.queryBuilder();
    queryBuilder.setWhere(queryBuilder.where().eq(Usuario.NOMBRE, "Fede"));
    List usuarios = dao.query(queryBuilder.prepare());
    if (usuarios.isEmpty()) {
        Log.d(TAG, "No se encontraron usuarios con nombre = Fede");                
    } else {
        Log.d(TAG, "Recuperado usuarios con nombre = Fede " + usuarios);                
    }
} catch (SQLException e) {
    Log.e(TAG, "Error creando usuario");
}

 
Actualizar y eliminar objetos
 

Dao dao;
try {
    dao = getHelper().getUsuarioDao();
    usuario.setNombre("Pedro");
    dao.update(usuario);
    Log.d(TAG, "Usuario modificado: " + usuario.getNombre());
    dao.delete(usuario);
} catch (SQLException e) {
    Log.e(TAG, "Error creando usuario");
}

 

Conclusiones

Como podéis ver ORMLite es muy sencillo e intuitivo. En próximas entradas intentaremos profundizar en su uso. También os recomendaría que os dierais una vuelta por la documentación oficial y por supuesto a las preguntas y respuestas sobre esta librería en stackoverflow.

Acerca del autor

FedeProEx

Ingeniero Informático en la Universidad de Sevilla, programador Java y amante del Heavy Metal. Soy desarrollador android fuera del horario de trabajo con algunas aplicaciones en el market como Tiempo AEMET o aconTags

  • jose luis sepulveda

    excelente aporte, me gustaría tener un poco mas de documentación e implementación en elementos del activity. agradezco cualquier aprte en eso

  • Jesus Alver

    Como funciona like en ORMLite.

    Gracias

  • Jorge

    Interesante articulo, solo tengo una duda, que librería es mas potente para trabajar ORMLite, ADA Framework o Android DataFramework, caul de estas tres aconsejarias usar?

  • http://twitter.com/jackgris2 Gabriel Pozo (@jackgris2)

    Yo también suelo utilizar este ORM, y creo que este post es un excelente resumen del mismo, muy bueno el post ;)

  • Jose

    Muchas gracias por tu tiempo. Muy bueno el post.
    Haber si haces una segunda parte.
    Saludos.

  • José Luis

    Muy bueno.
    Me gustaria que realizaras la segunda parte, para ver como se relacionan las tablas usuario y grupo.

    Gracias.

  • http://twitter.com/Chemaclass Chema (@Chemaclass)

    Un post excelente. ¡Gracias!

  • fernando

    Hola
    tienes el codigo de este ejemplo?

    • FedeProEx

      Hola!, lo siento pero no tengo el proyecto. Un saludo

  • http://mobiledevstories.wordpress.com mobiledevstories

    También uso SQLite y estoy de acuerdo contigo en que es una libreria estupenda. Lastima la falta de documentación que ha incrementado el coste de aprendizaje… Me hubiera venido de lujo haber conocido este articulo el primer dia que la descargué :P

  • http://gravatar.com/carlostrx carlostrx

    Me has convencido la próxima app, cambio de ada a ormlite para ver que tal. Pinta muy bien. Muchas gracias por el tuto, es genial.

  • sergio

    La parte de recuperar el helper, en donde va??

    • FedeProEx

      El código está pensado para que se recupere en la Activity

  • Antonio

    Hoy me parece interesante pero hay cosas que no entiendo, cuando dices que se pude crear una clase abstracta y copiar el código ese, si ya extiendes de Activity como vas a extender de la clase
    abstracta para los métodos?

    • FedeProEx

      Haciendo que la clase abstracta extienda de Activity ;)

  • norma

    subi el codigo del proyecto!!!!

  • JITLE NOREGA

    Buenos dias.

    Me gustaria saber como manejar las excepciones en ORMLite. Quiero identificar los errores y no he podido. agradezco la ayuda..

  • eduardo

    Hola, una pregunta si extiendo la activity de OrmLiteBaseActivity ya no es necesario que yo libere manualmente los recursos de getHelper().
    Saludos

  • Juan García Carmona

    Gran artículo, gracias, me ha servido para cambiar de parecer y pasar mi código a usar ORMLite (pensaba usar Sugar ORM por si simplicidad pero hace aguas por muchos sitios)…

    Para los que usen Android Studio se puede incluir ORM lite desde gradle añadiendo referencias tanto a android como a core (para poder extender OrmLiteSqliteOpenHelper)… Escribo desde el móvil, por eso no pongo el código de gradle.

    Saludos

  • Iván Rivas

    Hola me parece muy bueno tu tutorial, quisiera saber que pasa si agrego una nueva tabla, por ejemplo grupo, se realiza un upgrade en automático? o yo debo preparar el upgrade para que no se pierdan los datos de las otras tablas?, saludos.

  • Yaniel Blanco Fernández

    Buenas el post me parece super interesante y habia tenido la experiencia de trabajar con ADA Framework. Solo me queda una duda y no he podido solverla. Cuando llamo al método getDao(Bean.class) dice que este no está definido. Más específico:
    ¨The method getDao es undefined for type DBHelper.¨ Yo desarrollo con el eclipse ADT.
    Saludos y gracias de antemano.