Skip to content

Commit 903a80f

Browse files
committed
Merge pull request pullrequest#12 from johanpoirier/master
Please publish my ORMLite article.
2 parents 0eefb11 + 80078aa commit 903a80f

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
_site/
2+
Thumbs.db
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
---
2+
layout: post
3+
title: ORMLite pour Android
4+
author: johanpoirier
5+
tags: [ormlite, android, orm]
6+
---
7+
8+
Dans le cadre de mon étude des divers frameworks pour le développement java sur Android (voir [mon article précedent](http://pullrequest.org/2012/02/29/tour-d-horizon-des-frameworks-java-pour-android.html)), j'ai commencé par introduire ORMLite pour Android dans mon application démo (à voir [ici](https://github.com/johanpoirier/Android-Booking-Demo) sur Github).
9+
[ORMLite](http://ormlite.com/sqlite_java_android_orm.shtml) pour Object Relational Mapping Lite est un ORM léger pour Java supportant plusieurs bases de données dont SQLite qui nous intéresse directement pour nos développements Android.
10+
11+
<p class="center">
12+
<img src="/public/img/2012-05-09-ormlite-pour-android/ORMLite_logo.png" border="0" />
13+
</p>
14+
15+
16+
## L'utilisation d'ORMLite
17+
18+
### Annoter son modèle
19+
20+
En Android natif, les objets de notre modèle sont de simples POJO tels que :
21+
22+
{% highlight java %}
23+
public final class User {
24+
25+
private int id;
26+
private String firstName;
27+
private String lastName;
28+
private String login;
29+
private String password;
30+
31+
public int getId() {
32+
return id;
33+
}
34+
35+
public void setId(int id) {
36+
this.id = id;
37+
}
38+
39+
public String getFirstName() {
40+
return firstName;
41+
}
42+
43+
public void setFirstName(String firstName) {
44+
this.firstName = firstName;
45+
}
46+
...
47+
}
48+
{% endhighlight %}
49+
50+
Nous allons utiliser les annotations d'ORMLite pour enrichir notre POJO :
51+
52+
{% highlight java %}
53+
// We declare a specific DAO, not the one generated by ORMLite
54+
@DatabaseTable(daoClass = UserDaoImpl.class, tableName = User.TABLE_NAME)
55+
public final class User implements Serializable {
56+
57+
private static final long serialVersionUID = -3366808703621227882L;
58+
59+
public static final String TABLE_NAME = "users";
60+
61+
@DatabaseField(generatedId = true, columnName = Schema.ID)
62+
private long id;
63+
64+
@DatabaseField(columnName = Schema.FIRST_NAME)
65+
private String firstName;
66+
67+
@DatabaseField(columnName = Schema.LAST_NAME)
68+
private String lastName;
69+
70+
@DatabaseField(columnName = Schema.LOGIN)
71+
private String login;
72+
73+
@DatabaseField(columnName = Schema.PASSWORD)
74+
private String password;
75+
76+
public User(long id) {
77+
this.id = id;
78+
}
79+
80+
public long getId() {
81+
return id;
82+
}
83+
84+
public void setId(long id) {
85+
this.id = id;
86+
}
87+
88+
public String getFirstName() {
89+
return firstName;
90+
}
91+
92+
public void setFirstName(String firstName) {
93+
this.firstName = firstName;
94+
}
95+
96+
...
97+
}
98+
{% endhighlight %}
99+
100+
Nous avons donc ajouté @DatabaseTable et @DatabaseField sur notre classe. Les annotations sont explicitement nommées et documentées [ici](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#SEC8).
101+
102+
Nous pouvons également déclarer des relations entre objets du modèle :
103+
104+
{% highlight java %}
105+
// Foreign field
106+
@DatabaseField(canBeNull = false, foreign = true)
107+
private Account account;
108+
109+
// Foreign collection
110+
@ForeignCollectionField(eager = true, orderColumnName = Booking.Schema.CHECKIN_DATE)
111+
private ForeignCollection<Booking> bookings;
112+
{% endhighlight %}
113+
114+
Quelques points négatifs sur les relations :
115+
116+
- par défaut, les objets étrangers ne sont pas requêtés, seul l'id est renseigné dans un objet vide (par exemple, pour un object User, l'object Account aura seulement son id de renseigné). Il est quand même possible d'activer la mise à jour automatique des objets étrangers (voir le [foreignAutoRefresh](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_2.html#ANC6)).
117+
- pas moyen de préciser l'ordre de tri de la collection quand elle est retournée (certes il est possible de le faire en Java mais le faire en SQL aurait été beaucoup plus simple et rapide). Il est également possible d'utiliser le DAO pour mettre à jour cet objet :
118+
119+
{% highlight java %}
120+
accountDao.refresh(user.getAccount());
121+
{% endhighlight %}
122+
123+
La gestion des relations entre objets est une des bases d'un ORM et c'est un vrai plus par rapport à de l'android natif.
124+
125+
126+
### Les DAO
127+
128+
ORMLite s'occupe du cycle de vie des DAO :
129+
130+
- la création via le DaoManager
131+
- une fois créés, ils sont réutilisés car leur création est une opération côuteuse
132+
133+
Exemple d'appel au DaoManager :
134+
135+
{% highlight java %}
136+
// Long is the class of the ID field of the object User
137+
Dao<User, Long> userDao = DaoManager.createDao(connectionSource, User.class);
138+
{% endhighlight %}
139+
140+
Il est également possible (et conseillé) de définir ses propres DAO avec une interface et une implémentation :
141+
142+
{% highlight java %}
143+
// UserDao inteface that extends DAO
144+
public interface UserDao extends Dao<User, Integer> {
145+
ConnectionSource getConnectionSource();
146+
User findByLogin(String login);
147+
}
148+
149+
// The UserDaoImpl must also extends BaseDaoImpl
150+
public class UserDaoImpl extends BaseDaoImpl<User, Integer> implements UserDao {
151+
152+
private ConnectionSource connectionSource;
153+
154+
public UserDaoImpl(ConnectionSource connectionSource, DatabaseTableConfig<User> tableConfig) throws SQLException {
155+
super(connectionSource, tableConfig);
156+
this.connectionSource = connectionSource;
157+
}
158+
159+
@Override
160+
public ConnectionSource getConnectionSource() {
161+
return connectionSource;
162+
}
163+
164+
@Override
165+
public User findByLogin(String login) {
166+
try {
167+
List<User> users = this.queryForEq(Schema.LOGIN, login);
168+
if(!users.isEmpty()) {
169+
return users.get(0);
170+
}
171+
} catch (SQLException e) {
172+
Log.e(C.LOG_TAG, "Error querying users for " + login + " login.", e);
173+
}
174+
return null;
175+
}
176+
}
177+
{% endhighlight %}
178+
179+
Dans le cas de l'utilisation d'un Dao spécifique, il faut obligatoirement préciser la classe dans l'annotation @DatabaseTable de l'objet User (daoClass). C'est assez moyen car maintenant notre objet du modèle est lié à l'implémentation de notre DAO (!). On va dire que dans le développement mobile, il faut savoir faire des concessions...
180+
181+
Un dernier petit détail : il est possible d'activer un cache au niveau des DAO (voir la [doc](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_5.html#SEC53)).
182+
183+
{% highlight java %}
184+
public HotelDao getHotelDao() {
185+
if (hotelDao == null) {
186+
try {
187+
hotelDao = getDao(Hotel.class);
188+
hotelDao.setObjectCache(true);
189+
} catch (Exception e) {
190+
Log.e(C.LOG_TAG, "unable to get hotel DAO", e);
191+
}
192+
}
193+
return (HotelDao) hotelDao;
194+
}
195+
{% endhighlight %}
196+
197+
### Les transactions
198+
199+
ORMLite fournit un mécanisme de transactions simple (voir la [doc](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_5.html#SEC52)) :
200+
201+
{% highlight java %}
202+
final Hotel hotel = new Hotel();
203+
TransactionManager.callInTransaction(connectionSource,
204+
new Callable<Void>() {
205+
public Void call() throws Exception {
206+
// new hotel
207+
hotelDao.create(hotel);
208+
209+
// set the hotel to the booking
210+
booking.setHotel(hotel);
211+
212+
// update our booking
213+
bookingDao.update(booking);
214+
215+
return null;
216+
}
217+
});
218+
{% endhighlight %}
219+
220+
### Le QueryBuilder
221+
222+
Le QueryBuilder a pour but de construire des requêtes SQL sans faire du SQL (ou presque). Il permet de chaîner les appels de méthodes afin de rendre plus lisible la requête :
223+
224+
{% highlight java %}
225+
queryBuilder.where().eq(User.Schema.LOGIN, "demo").and().eq(User.Schema.PASSWORD, "1234");
226+
{% endhighlight %}
227+
228+
Pas besoin de s'étaler sur cette fonctionnalité qui n'est pas spécifique à Android. [La documentation](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_3.html#SEC32) est d'ailleurs très complète à ce sujet.
229+
230+
## L'intégration à Android
231+
232+
### Création et mise à jour de schémas
233+
234+
ORMLite fournit un **OrmLiteSqliteOpenHelper** qui étend le SQLiteOpenHelper d'Android et qui permet de créer automatiquement le schéma SQLite et de le mettre à jour. Cette classe surcharge les onCreate et onUpgrade pour les besoins de SQLite. D'autres outils sont disponibles comme **TableUtils** qui permet de créer, vider et supprimer des tables.
235+
236+
### Accès aux DAO dans les activités
237+
238+
ORMLite fournit une classe qui surcharge Activity pour fournir à nos activités un accès direct à la couche de persistence : **OrmLiteBaseActivity**.
239+
Pour faire court, elle fournit un Helper qui nous permet de récupérer le OrmLiteSqliteOpenHelper du chapitre précédent. Ce helper est géré par la classe OpenHelperManager qui permet de gérer le cycle de vie du helper afin qu'une seule instance soit présente au sein de l'application.
240+
241+
{% highlight java %}
242+
// MyOrmLiteSqliteOpenHelper is our implementation of OrmLiteSqliteOpenHelper
243+
public class LoginActivity extends OrmLiteBaseActivity<MyOrmLiteSqliteOpenHelper> {
244+
245+
public void onCreate(Bundle savedInstanceState) {
246+
int userId = 42;
247+
User user = ((UserDao) getHelper().getDao(User.class)).queryForId((int) userId);
248+
}
249+
}
250+
{% endhighlight %}
251+
252+
Si vous voulez avoir accès à vos DAO ailleurs que dans vos activités, vous pouvez donc utiliser la classe **OpenHelperManager** :
253+
254+
{% highlight java %}
255+
OpenHelperManager.getHelper(context, MyOrmLiteSqliteOpenHelper.class);
256+
{% endhighlight %}
257+
258+
Par contre, il faudra bien prendre soin de libérer le helper afin de fermer la connection à la bdd (OpenHelperManager.release()).
259+
260+
261+
## Performances
262+
263+
### Les trucs à savoir
264+
265+
- Il peut y avoir un temps de chargement très long des DAO au démarrage de l'application. Afin de contrer ça, ORMLite fournit un utilitaire permettant de générer un fichier plat de description de votre schéma qui sera ensuite utilisé au démarrage de l'application pour charger les DAO au lieu d'utiliser la réflexion qui est extrêment coûteuse sur Android. Le fichier généré s'appelle **ormlite_config.txt** et sera stocké dans le répertoire res/raw/. Il faudra ensuite le référencer dans votre classe OrmLiteSqliteOpenHelper (pour de plus amples informations, voir [la doc d'ORMLite](http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_4.html#SEC41)).
266+
267+
- Vous pouvez préciser le nom de votre classe OrmLiteSqliteOpenHelper dans le fichier res/values/strings.xml :
268+
269+
{% highlight xml %}
270+
<string name="open_helper_classname">org.pullrequest.android.bookingnative.domain.DatabaseHelper</string>
271+
{% endhighlight %}
272+
273+
Ceci permettra d'éviter à ORMLite la recherche de cette classe par réflexion (encore une fois).
274+
275+
### Benchmarks
276+
277+
J'ai testé l'insertion de 1000 objets simples avec et sans transaction :
278+
279+
- sans tx : 1000 objets en 97874 ms, soit 98 ms par objet
280+
- avec tx : 1000 objets en 842 ms, soit 0,8 ms par objet
281+
282+
On voit que l'utilisation des transactions est bien plus rapide dans le cas d'insertions en masse.
283+
284+
Reste à comparer avec du Android natif :
285+
286+
- sans tx : 1000 objets en 92468 ms, soit 92 ms par objet
287+
- avec tx : 1000 objets en 1178 ms, soit 1,2 ms par objet
288+
289+
Ces chiffres donnent juste un ordre d'idée car il faudrait beaucoup plus de tests pour avoir un benchmark plus précis, mais ils me confortent dans l'idée qu'ORMLite ne pénalise pas mon application en terme de performances.
290+
291+
### Le ressenti utilisateur
292+
293+
Comme écrit dans le chapitre précédent, je ne ressens aucune différence notable en terme d'utilisation de l'application.
294+
295+
296+
## Conclusion
297+
298+
ORMLite est donc un outil assez complet et assez léger (310 Ko) pour nos applications Android. Il nous permet d'écrire un code plus propre, plus lisible et plus léger. Il nous rapproche aussi de l'architecture utilisée dans nos développements Java côté serveur.
299+
300+
Pour aller plus loin, nous pourrons coupler ORMLite avec un framework d'injection comme RoboGuice, que l'on étudiera dans un prochain article. Stay tuned !
54.2 KB
Loading

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy