Sauvegarder simplement en base de données avec ORMLite sous Android

Bonjour,

Je voudrais vous parler d’une bibliothèque très pratique pour sauvegarder des données dans la base SQLite d’un téléphone Android. Cette bibliothèque s’appelle ORMLite.

Je vous ai déjà parlé d’une autre bibliothèque pratique pour faire un parsing JSON : JacksonPaser.
Les deux bibliothèques peuvent s’associer parfaitement, ce qui offre un bon socle pour commencer un projet Android.

Ormlite supporte les connections JDBC à MySQL, Postgres, H2, SQLite, Derby,
HSQLDB, Microsoft SQL Server.
Mais ce qui nous intéresse ici c’est qu’ORMLite supporte les appels natifs à la base de données SQLite sous Android.

Si vous avez déjà essayé de faire de la sauvegarde en base sous android, vous vous êtes sûrement aperçu combien c’est fastidieux.
Personnellement je ne trouve pas cela très pratique. Je préfère utiliser un ORM (Object Relational Mapping) qui fait un mapping entre mes objets métier et les enregistrements en base de données.

Certes on peut avoir des dégradations de performances, mais au vu du confort apporté au niveau de la programmation, le choix est vite fait.

Installation

Il faudra télécharger 2 fichiers jar, les déposer dans un répertoire lib et les ajouter dans le CLASSPATH.
Vous trouverez les 2 fichiers suivant ici http://ormlite.com/releases/

– ormlite-android-X.XX.jar
– ormlite-core-X.XX.jar

Les Annotations

Pour configurer le mapping objet on utilise des annotations.
Les annotations principales sont :
– @DatabaseTable
@DatabaseField

L’annotation @DatabaseTable se dépose devant la déclaration d’une classe métier, et prend en paramètre le nom de table sur laquelle on veut faire le mapping.

@DatabaseTable(tableName = "le_nom_de_ma_table")  

L’annotation @DatabaseField permet de configurer le mapping entre une colonne d’une table SQL, et le membre d’une classe.
@DatabaseField peut prendre plusieurs paramètres :

  • columnName= « le_nom_de_ma_colonne » indique le nom de la colonne sur laquelle on fait le mapping
  • generatedId = true/false : indique que le membre de la classe se correspond à un champ index auto-incrémenté
  • canBeNull = true/false : indique si champ peut être null, et permet de générer des exceptions
  • defaultValue = « 0 » : indique une valeur par défaut si le membre n’a pas été affecté avant l’enregistrement en base de l’objet.
  • unique = true pour définir une colonne UNIQUE
  • uniqueIndexName = « mon_ensemble_unique » permet de déclarer qu’un ensemble de membres est unique. C’est utile si on dépose l’annotation au moins sur 2 membres de la classe

Voici un exemple de classe métier annoté:

@DatabaseTable(tableName = "category")
public class Category extends DataEnveloppe implements Parcelable{
	
	public static final String   COLUMN_CATID         = "catID";
	
	@DatabaseField(generatedId=true)
	private int mRowid;
	
	@DatabaseField(columnName = COLUMN_CATID, canBeNull=false)
	private String mCatID;
	
	@DatabaseField(columnName = "title", canBeNull=false) 
	private String mTitle;
....
}

Implémentation de OrmLiteSqliteOpenHelper

Vous aurez besoin de créer une classe qui hérite de OrmLiteSqliteOpenHelper.
Cette classe gère la création de la base à la première exécution, et gère la migration de la base lors des mises à jour de l’application.

Pour faire cette gestion, des callback sont appelés automatiquement, il faut implémenter les deux méthodes suivantes:
– onCreate(SQLiteDatabase sqliteDatabase, ConnectionSource
connectionSource)

– onUpgrade(SQLiteDatabase database, ConnectionSource
connectionSource, int oldVersion, int newVersion).

Pour créer les tables, il suffit d’appeler une méthode utilitaire qui va créer pour nous la table associé à une classe ; à condition d’avoir mis les annotations sur cette classe.

Par exemple :

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

	...
	@Override
	public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
		try {
			EtLog.d(TAG, "Tables created in %s", DATABASE_NAME);
			TableUtils.createTable(connectionSource, Category.class);
			TableUtils.createTable(connectionSource, MonObjetMetier.class);

		} catch (SQLException e) {
			Log.e(TAG, "Can't create database", e);
			throw new RuntimeException(e);
		}
	}
	...

Pour gérer la mise à jour voici un exemple d’implémentation. Attention dans cet exemple, on va supprimer la table et la recréer. Ce n’est pas toujours ce que l’on veut faire. Si on veut garder les données de l’application lors de la mise à jour, il faudra implémenter un algorithme de migration des données.

	@Override
	public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource,
		int oldVersion, int newVersion) {
		try {
			TableUtils.dropTable(connectionSource, Category.class, true);
			onCreate(db);
		} catch (SQLException e) {
			Log.e(TAG, "Impossible to drop database", e);
			throw new RuntimeException(e);
		}
	}

Obtenir une instance de OpenHelperManager

Afin d’éviter d’ouvrir plusieurs connections simultanément à la même base, nous allons utiliser la classe OpenHelperManager qui va surveiller les utilisations du Helper.
Pour avoir un accès à une instance de OpenHelperManager il faut appeler getHelper() dans vos activités.
Pour avoir accès à cette méthode cela implique que vos activités doivent hériter de OrmLiteBaseActivity, OrmLiteBaseListActivity, OrmLiteBaseService, ou OrmLiteBaseTabActivity.

Cet héritage obligatoire peut être très contraignant. Notamment si vous utilisez une autre bibliothèque qui, elle aussi, demande à vos activités d’hériter d’une certaine classe.
Pour éviter de se retrouver bloqué ainsi, je vous conseille d’utiliser une autre méthode.
Il est possible de déclarer une classe comme OrmLiteSqliteOpenHelper avec la méthode statique suivante.

setOpenHelperClass(Class openHelperClass)

Il est nécessaire d’appeler cette méthode le plus tôt possible dans votre application, ainsi je recommande de le faire dans le onCreate() de votre classe Application.

Rappel:
Si vous ne le savez pas, il est possible d’avoir une instance qui représente le contexte de votre application. Il faut créer une classe qui hérite de Application et déclarer cette classe dans le manifeste. Je vous conseille de créer cette classe sous la forme d’un singleton.

    
...

Pensez à créer un assesseur pour obtenir l’instance au Helper il sera fournir par un appel à OpenHelperManager.getHelper()

Voilà ce que ça donne au niveau du code :

	private static MonApplication mInstance;
	private OrmLiteSqliteOpenHelper mOrmLiteHelper;

	@Override
	public void onCreate() {
		super.onCreate();
		mInstance = this;
        DataManager.getInstance().setApplicationContext(this);
        PreferencesManager.getInstance().setApplicationContext(this);
        FaceBookUtils.initContext(this);
        initData();
	}

	public static MonApplication getInstance() {
		return mInstance;
	}

	private void initData() {
        DataManager.getInstance().setOpenHelperClass(DatabaseHelper.class);
	}
	
	public OrmLiteSqliteOpenHelper getHelper() {

        if(mOrmLiteHelper == null) {
            mOrmLiteHelper = OpenHelperManager.getHelper(this);
        }
        return mOrmLiteHelper;
    }
	
	@Override
	public void onTerminate() {
		super.onTerminate();
		if (mOrmLiteHelper != null) {
			OpenHelperManager.releaseHelper();
		}
	}


Sauvegarder un objet métier

Ensuite pour faire un enregistrement d’une entité d’un objet métier, il suffit d’appeler la méthode save() que l’on aura implémenté comme ci-dessous dans notre classe métier :
Notez que c’est la même méthode pour un update ou un create .

	protected void save() throws SQLException {
		OrmLiteSqliteOpenHelper helper = getHelper();
		if (helper instanceof DatabaseHelper) {
			DatabaseHelper absHelper = (DatabaseHelper) helper;
			@SuppressWarnings("unchecked")
			Dao dao = (Dao) absHelper
					.getDao(getClass());
			dao.createOrUpdate(this);
		} else {
			Log.e("",
					"Object not created nor updated! Your Helper must inherit from AbstractDatabaseHelper.");
		}
	}

Et pour faire un select

Depuis l’objet Dao on appelle la méthode queryBuilder() pour obtenir un QueryBuilder ;
Ensuite il existe plusieurs méthodes utiles pour ajouter une clause WHERE, un LIMIT ou un ORDER BY.

Pour construite une clause WHERE il faudra utiliser également les méthodes disponibles depuis l’objet Where comme dans l’exemple ci-dessous.

public List getListCategoryFromDb() {
	List categoryListFromCache = new ArrayList();
	try {
		
		QueryBuilder queryBuilder = getDao().queryBuilder();
		
		@SuppressWarnings("rawtypes")
		Where where = queryBuilder.where();
		where.eq(Category.COLUMN_SUB_CAT, "1"); // Categorie de niveau 1

		PreparedQuery preparedQuery = queryBuilder.prepare();
		categoryListFromCache = getDao().query(preparedQuery);
		setList(categoryListFromCache);
	} catch (Exception e) {
		// TODO: handle exception
	}
	return categoryListFromCache;
}

Conclusion

Avec cette bibliothèque, une fois la classe OrmLiteSqliteOpenHelper implémentée, il ne vous reste plus beaucoup de lignes de code à écrire pour faire de l’enregistrement en base de donnée.
La méthode save() peut même être unifiée dans une classe parente de toutes vos classes métier(que vous avez besoin d’enregistrer en base).