Lors de la dernière I/O 2017, Google a annoncé la mise à disposition aux développeurs d'un ensemble de d'outils et composants regroupés sous le nom "Android Architecture Components".
Le but est d'aider les développeurs à développer des applications robustes, testables et maintenables permettant notamment de manipuler les données et intervenir sur le cycle de vie des écrans.
Dans le cadre de cette présentation, nous allons plus particulièrement nous intéresser au module "Room", une couche d'abstraction à SQLite (ORM) permettant de manipuler la base de données des applications et assurer la persistance des données.
Puisque Realm est aujourd'hui à la mode mais présente des limitations, nous comparerons les deux outils sur plusieurs angles :
• configuration
• gestion d'une entité avec une relation
• écriture des requêtes
• rapidité d'exécution
• gestion de la migration
• etc.
Nous verrons ainsi si Room est, à ce stade, prometteur ou si au contraire, Realm a encore de beaux jours devant lui.
8. Smart&Soft
Agence Digitale
2017
8
Définition
ORM
“Un mapping objet-relationnel est une technique de
programmation informatique qui crée l'illusion d'une base
de données orientée objet à partir d'une base de données
relationnelle en définissant des correspondances entre
cette base de données et les objets du langage utilisé”
Source : https://fr.wikipedia.org/wiki/Mapping_objet-relationnel
13. Smart&Soft
Agence Digitale
2017
13
Définition
Realm
“Realm Mobile Database is an alternative to SQLite and Core
Data. Thanks to its zero-copy design, Realm Mobile
Database is much faster than an ORM, and often faster than
raw SQLite.”
Source : https://realm.io/products/realm-mobile-database/
19. Smart&Soft
Agence Digitale
2017
19
Les points de
comparaison
COMPARAISON
1. Configuration
2. Gestion d’une entité avec des relations
3. Les requêtes
4. Rapidité d’exécution
5. Gestion de la migration
6. D’autres éléments (RxJava, poids, nombre de méthodes,
threading, etc.)
20. 2017
20
Smart&Soft
Agence Digitale
Realm
1. CONFIGURATION
1. Ajout d’une dépendance dans le fichier build.gradle du projet
dependencies {
classpath "io.realm:realm-gradle-plugin:4.2.0"
}
2. Application du plugin dans le fichier build.gradle du module
apply plugin: "realm-android"
3. Initialisation de Realm
Realm.init(this);
Realm.setDefaultConfiguration(
new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().schemaVersion(1).build());
21. 2017
21
Smart&Soft
Agence Digitale
Room
1. CONFIGURATION
1. Ajout d’une nouvelle source dans le fichier build.gradle du projet
maven { url "https://maven.google.com" } // google()
2. Ajout des dépendances dans le fichier build.gradle du module
compile("android.arch.persistence.room:runtime:1.0.0")
annotationProcessor("android.arch.persistence.room:compiler:1.0.0")
3. Initialisation de Room à remettre à plus tard !
23. 2017
23
Smart&Soft
Agence Digitale
Realm
2. Gestion d’une entité avec des relations
public class RealmCat
extends RealmObject
{
@PrimaryKey
private int id;
private int age;
private String name;
//Getters and setters
}
public class RealmDog extends RealmObject {
public enum RealmColor { Black, White }
@PrimaryKey
private int id;
private int age;
private String name;
private String color;
//Getters and setters
public RealmColor getRealmColor() {
return color != null ?
RealmColor.valueOf(color) :
null;
}
public void setRealmColor(RealmColor color){
this.color = color.toString();
}
}
24. 2017
24
Smart&Soft
Agence Digitale
Realm
2. Gestion d’une entité avec des relations
public class RealmPerson
extends RealmObject
{
@PrimaryKey
private int id;
private String name;
private RealmList<RealmDog> dogs;
private RealmList<RealmCat> cats;
//Getters and setters
}
25. 2017
25
Bilan provisoire
2. Gestion d’une entité avec des relations
• Modélisation très simple
• Gestion des relations efficaces
• Héritage impossible (contournement possible via des objets composites)
• Pas d’auto-incrémentation de la clef primaire
• Pas de gestion des énumérations
26. 2017
26
Smart&Soft
Agence Digitale
Room
2. Gestion d’une entité avec des relations
public abstract class RoomAnimal
{
@PrimaryKey
public int id;
public int age;
public String name;
}
@Entity(tableName = "cat")
public final class RoomCat
extends RoomAnimal
{
}
27. 2017
27
Smart&Soft
Agence Digitale
Room
2. Gestion d’une entité avec des relations
@Entity(tableName = "dog")
public final class RoomDog
extends RoomAnimal
{
public enum RoomColor
{
Black, White
}
@TypeConverters(RoomColorConverter.class)
public RoomColor color;
}
public final class RoomColorConverter
{
@TypeConverter
public RoomColor fromString(String color)
{
return color != null ?
RoomColor.valueOf(color) : null;
}
@TypeConverter
public String fromRealmColor(RoomColor color)
{
return color.toString();
}
}
28. 2017
28
Smart&Soft
Agence Digitale
Room
2. Gestion d’une entité avec des relations
@Entity(tableName = "person")
public final class RoomPerson
{
@PrimaryKey
public int id;
public String name;
}
public final class RoomPersonWithAnimals
{
@Embedded
public RoomPerson person;
@Relation(parentColumn = "id",
entityColumn = "id",
entity = RoomDog.class)
public List<RoomDog> dogs;
@Relation(parentColumn = "id",
entityColumn = "id",
entity = RoomCat.class)
public List<RoomCat> cats;
}
30. 2017
30
Smart&Soft
Agence Digitale
Room
2. Gestion d’une entité avec des relations
@Entity(
tableName = "Person_has_Cat",
primaryKeys = { "personId", "catId" },
foreignKeys = {
@ForeignKey(entity = RoomPerson.class,
parentColumns = "id",
childColumns = "personId",
onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = RoomCat.class,
parentColumns = "id",
childColumns = "catId",
onDelete = ForeignKey.CASCADE) })
public final class RoomPersonCat
{
public int personId;
public int catId;
}
@Entity(
tableName = "Person_has_Dog",
primaryKeys = { "personId", "dogId" },
foreignKeys = {
@ForeignKey(entity = RoomPerson.class,
parentColumns = "id",
childColumns = "personId",
onDelete = ForeignKey.CASCADE),
@ForeignKey(entity = RoomDog.class,
parentColumns = "id",
childColumns = "dogId",
onDelete = ForeignKey.CASCADE) })
public final class RoomPersonDog
{
public int personId;
public int dogId;
}
31. 2017
31
Bilan provisoire
2. Gestion d’une entité avec des relations
• Gestion de l’auto-incrémentation de la clef primaire
• Possibilité d’héritage
• Modélisation plus complexe
• Références d’objets impossible
(https://developer.android.com/training/data-storage/room/referencing-data.html)
32. 2017
32
Smart&Soft
Agence Digitale
Realm
3. Les requêtes
//1. Récupération d'une instance de Realm
final Realm realm = Realm.getDefaultInstance();
//2. Création d'une transaction
realm.beginTransaction();
//3. Exécution de la requête
realm.copyToRealmOrUpdate(persons);
//4. On termine la transaction et on ferme l'instance de Realm
realm.commitTransaction();
realm.close();
Possibilité d’utiliser executeTransaction pour une gestion des erreurs simplifiée
33. 2017
33
Smart&Soft
Agence Digitale
Realm
3. Les requêtes
Requête de sélection
Requête de mise à jour
final Realm realm = Realm.getDefaultInstance();
final RealmPerson persons = realm.copyFromRealm(realm.where(RealmPerson.class).equalTo("id", 299).findFirst());
realm.close();
final Realm realm = Realm.getDefaultInstance();
final RealmPerson persons = realm.where(RealmPerson.class).equalTo("id", 299).findFirst();
persons.setName(UUID.randomUUID().toString());
realm.close();
34. 2017
34
Smart&Soft
Agence Digitale
Realm
3. Les requêtes
Requête de suppression
final Realm realm = Realm.getDefaultInstance();
final RealmPerson persons = realm.where(RealmPerson.class).equalTo("id", 299).findFirst();
persons.deleteFromRealm();
realm.close();
35. 2017
35
Bilan provisoire
3. Les requêtes
• Écriture des requêtes hyper simplifié
• Possibilité de faire des requêtes dans le thread de l’UI
• Résultats “vivants” par défaut (attention au threading !)
• Impossibilité de faire des requêtes très fines :
• Récupérer les chiens noirs d’une personne
36. 2017
36
Smart&Soft
Agence Digitale
Room
3. Les requêtes
@Dao
public interface RoomGenericDao<T>
{
@Insert(
onConflict = OnConflictStrategy.REPLACE)
void insert(T bo);
@Insert(
onConflict = OnConflictStrategy.REPLACE)
void insertAll(T... bo);
@Delete
void delete(T bo);
@Update
void update(T bo);
}
@Dao
public interface RoomCatDao
extends RoomGenericDao<RoomCat>
{
@Query("SELECT * FROM cat")
List<RoomCat> getAll();
@Query("SELECT * FROM cat WHERE id = :id")
RoomCat findById(int id);
}
37. 2017
37
Smart&Soft
Agence Digitale
Room
3. Les requêtes
@Dao
public interface RoomDogDao
extends RoomGenericDao<RoomDog>
{
@Query("SELECT * FROM dog")
List<RoomDog> getAll();
@Query("SELECT * FROM dog WHERE id = :id")
RoomDog findById(int id);
}
@Dao
public interface RoomPersonDao
extends RoomGenericDao<RoomPerson>
{
@Query("SELECT * FROM person")
List<RoomPersonWithAnimals> getAll();
@Query("SELECT * FROM person WHERE id = :id")
RoomPersonWithAnimals findById(int id);
}
38. 2017
38
Smart&Soft
Agence Digitale
Room
3. Les requêtes
1. Création d’une classe abstraite pour exposer les DAO et les entités managés
@Database(entities = { RoomCat.class, RoomDog.class, RoomPerson.class, RoomPersonCat.class,
RoomPersonDog.class }, version = 1, exportSchema = false)
public abstract class MyRoomDatabase extends RoomDatabase {
public abstract RoomCatDao roomCatDao();
public abstract RoomDogDao roomDogDao();
public abstract RoomPersonDao roomPersonDao();
}
2. Instanciation de la classe
myRoomDatabase = Room.databaseBuilder(this, MyRoomDatabase.class, "myRoomDatabase").build();
39. 2017
39
Smart&Soft
Agence Digitale
Room
3. Les requêtes
Possibilité d’utiliser runInTransaction
//1. Création d'une transaction (facultatif) → Interdit dans le main thread
myRoomDatabase.beginTransaction();
//2. Exécution de la requête
myRoomDatabase.roomPersonDao().insertAll(person);
//3. On indique que tout s’est bien passé
myRoomDatabase.setTransactionSuccessful();
//4. On termine la transaction
myRoomDatabase.endTransaction();
40. 2017
40
Smart&Soft
Agence Digitale
Room
3. Les requêtes
Requête de sélection
Requête de mise à jour
final RoomPersonWithAnimals personWithAnimals =myRoomDatabase.roomPersonDao().findById(299);
final RoomPersonWithAnimals personWithAnimals =myRoomDatabase.roomPersonDao().findById(299);
personWithAnimals.person.name = UUID.randomUUID().toString();
myRoomDatabase.roomPersonDao().update(personWithAnimals.person);
Requête de suppression
final RoomPersonWithAnimals persons =myRoomDatabase.roomPersonDao().findById(43);
myRoomDatabase.roomPersonDao().delete(persons.person);
41. 2017
41
Smart&Soft
Agence Digitale
Room
3. Les requêtes
final List<RoomPersonWithAnimals> personsWithAnimals = new ArrayList<>();
for (int index = 0; index <NUMBER_OF_ITEMS; index++) { /*...*/ }
//1. Création d'une transaction (facultatif)
myRoomDatabase.beginTransaction();
//2. Insertion des éléments
for (RoomPersonWithAnimals personAnimals : personsWithAnimals) {
myRoomDatabase.roomPersonDao().insert(personAnimals.person);
myRoomDatabase.roomCatDao().insertAll(personWithAnimals.cats.toArray(newRoomCat[personWithAnimals.cats.size()]);
myRoomDatabase.roomDogDao().insertAll(personWithAnimals.dogs.toArray(newRoomDog[personWithAnimals.dogs.size()]));
for (RoomCat cat : personWithAnimals.cats) {
myRoomDatabase.roomPersonCatDao().insert(new RoomPersonCat(personWithAnimals.person.id, cat.id));
}
for (RoomDog dog : personWithAnimals.dogs){
myRoomDatabase.roomPersonDogDao().insert(new RoomPersonDog(personWithAnimals.person.id, dog.id));
}
}
//3. On termine la transaction
myRoomDatabase.setTransactionSuccessful();
myRoomDatabase.endTransaction();
42. 2017
42
Bilan provisoire
3. Les requêtes
• Rude...
• Par défaut impossibilité de faire des requêtes dans le thread de l’UI
• SQL
44. 2017
44
Smart&Soft
Agence Digitale
3. Les performances
Room Realm
Insertion de beaucoup de
données
10 991 ms 2 587 ms
Insertion d’une ligne 6 ms 4 ms
Sélection 3 ms 26 ms
Suppression 7 ms 15 ms
Mise à jour 4 ms 4 ms
45. 2017
45
Realm
4. Gestion de la migration
• Possibilité de détruire la base de données existante
• Possibilité de gérer une migration via l’interface RealmMigration
• Migration orientée objet
46. 2017
46
Room
4. Gestion de la migration
• Possibilité de gérer plusieurs migrations via la classe Migration
• Migration orientée SQL
• Possibilité de faire des tests unitaires sur la migration d’une base de données
• Possibilité d’exporter le schéma de base de données au format JSON