📜 ⬆️ ⬇️

What is in ORM mine? The near-scientific approach of choosing ORM for Android

The choice of tools that one way or another will be needed in the development is one of the main preparatory stages at the start of the new Android project.
If you are developing an application that needs to store a large number of entities in one form or another, you will not avoid using databases. Unlike peers who develop for iOS, Android programmers do not have convenient tools that facilitate the storage of objects like Core Data provided by the platform (except for the Content Provider, why it doesn’t count, it will be further). Therefore, many Android developers resort to using third-party ORM solutions in their projects. About what you should look at when choosing a library for your project, and will be discussed in this article.



First of all, I would like to make sure that the choice of ORM is not contrived, and we looked at all the available means for storing data that are provided by the Android SDK out of the box.

Consider them as the complexity of writing the implementation of such a repository increases. Note that I did not specifically consider the usual files in this comparison. Of course, they are an ideal option for lovers of their own bicycles living in the philosophy of Not Invented Here, but in our case they will be too low-level.
')
Shared Preferences
http://developer.android.com/guide/topics/data/data-storage.html#pref
Key-value storage for primitive data types. Supported
Integer, Long, Float, Boolean, String and StringSet. The main purpose is to store a certain state of the application and user settings. At its core, it represents a wrapper over an XML file, which is located in the “private” folder of your application in the subdirectory shared-prefs. For storage of a set of the same structured data is not suitable.

SQLite databases
http://developer.android.com/guide/topics/data/data-storage.html#db
SQLite is a standard Android database. The framework provides several helpers classes that facilitate working with the database: SQLiteOpenHelper, ContentValues, etc. However, even the use of these helpers will not relieve you of the obligation to write a huge amount of sample code, independently monitor the creation and modification of tables, create methods for operations, methods for searching, etc. Thus, the code for applications that use only standard SQLite tools in Android is becoming increasingly difficult to maintain when adding new and changing old entities.

Content Provider
http://developer.android.com/guide/topics/providers/content-providers.html
Content Provider is a layer above the real data store. It may seem that the Content Provider is a “boxed” implementation of the ORM technology, but this is far from the case. If you use SQLite as a repository for the Content Provider, you will have to independently implement the logic for creating, updating tables and basic CRUD operations. In most cases, using the Content Provider without special generators will not only not save time on development and support, but, perhaps, will spend much more than writing your own SQLiteOpenHelper implementation. However, Content Provider allows you to use some convenient platform classes - such as AsyncQueryHandler , CursorLoader , SyncAdapter, and others.

We are convinced that we have reviewed all the data storage tools available in the Android SDK and come to the conclusion: SQLite provides all the necessary conditions for organizing the storage of structured data of the same type (surprisingly, isn't it?). However, as mentioned above, using SQLite in Android requires a large amount of code and constant support, so we will try to make our lives easier by resorting to a third-party solution.

This is where the ORM technique comes to help - Object Relational Mapping. Its implementation, in essence, creates the impression of an object database, having basically a common relational database. ORM, providing a higher level of abstraction, is designed to save programmers from the need to convert data model objects into scalar values ​​supported by the database, allow them to write less generic code and not worry about the structure of tables.

Having decided on the technology, we turn to this question on the Internet and select 4 libraries:

How to choose the right library and not regret your decision if it is too late? In similar articles, I stumbled upon only high-quality comparisons of libraries. However, in my opinion, the ORM library should be balanced in terms of convenience and performance. Therefore, a comparison of these solutions from an API point of view only, without a performance analysis, would be incomplete. But first, a small digression about why you should still pay attention to the performance of ORM.

Why all this?


Why evaluate ORM performance? Obviously, in the end, everything will rest on the limitation of SQLite itself, and that, in turn, will restrict the file system (this is a single-file database). However, as it turned out, these natural limitations are still very far away.

Before proceeding to the description of the test I conducted and its results, I would like to tell a little story about why we began to pay attention to the ORM performance, which we use in our projects and in trying to improve it.

One day, Sebbia came to us to develop a kind of application that consumes a unified REST API for all clients. Of all the existing ORMs on the market, it was decided to use the ActiveAndroid, proven by time and fully satisfying at that time. The main essence of the application (for simplicity, let's call it the “Entity”) is a certain state of the set of other entities of the system, parts of which (the “Owners of the Entity”) were represented only by the identifiers of these entities. It was assumed that when requesting “Entity” the client will load the “Owners of the entity” automatically if they were not found in the application cache. In mobile devices, I would like to avoid this situation - in view of the energy costs of sending a new request. Any changes to the API would lead to potential compatibility issues with other clients. Therefore, we decided to load and cache the list of “Owners of the entity” before the need arises to load the “Entities” themselves. Most logical such an operation to perform when you first start the application. It is worth making a reservation that the list of all the “Owners of the entity” was given completely, not page by page. What was our surprise when we saw how long this list is stored in the database!

After checking the code again and making sure that the list is saved once inside the transaction, ActiveAndroid came under our suspicion. In general, the reason for the drop in application performance while maintaining a voluminous list was reflection, namely, obtaining the values ​​of the object fields and filling them with ContentValues. Replacing the code that uses reflection, the code generated by the handwritten plug-in for Eclipse, we received an almost two-fold increase in performance - from ~ 38 seconds to 20 seconds. Having once again been convinced that it is worthwhile to take a closer look at how open-source libraries are designed from the inside, let us turn to the substantive part of the article.

Arms race


Of all the selected libraries, GreenDao stands apart - after all, it is the only one among the solutions presented who uses code generation. In order not to make fast conclusions - GreenDao is the fastest, and forget about the rest , we decided to format the approach with code generation (used in the ActiveAndroid project described above) as a separate fork and pull request to the official repository, in addition to adding other useful functions: “One to many”, “many to many” and automatic data migration when changing the structure of entities and, accordingly, their tables. The resulting fork, which I will call “ActiveAndroid.Sebbia” for simplicity, has been added to the test.

It is time to talk about the test. It checks how quickly a particular library can save test entities during a SQLite transaction and performs the reverse operation. 1000 test objects are added to the newly created database, which, after clearing the cache in the ORM memory, are read and checked for “correctness” of the data. Each test subject is tested 10 times, the average result time is taken as the final result. Test objects consist of two fixed-length text fields, one date field, and one byte array, obtained from the object being serialized. Initially it was assumed that ORM itself had to convert a Serializable object into an array of bytes, but it turned out that neither GreenDao nor SugarORM had such an opportunity, so this idea had to be abandoned.

In order to understand the maximum possible speed of a “row object in a table-object” operation, which can be achieved using standard Android SDK tools, an example using SQLiteOpenHelper and compiled SQLiteStatement was added to the comparison. The project itself and all the version libraries on which the comparison was made is located on GitHub .


The results are quite predictable, No ORM solution is the fastest, although it is not much ahead of GreenDAO. By and large, the code for these solutions is the same, except that GreenDAO provides a more user-friendly interface.

In turn, GreenDAO takes the second place in the overall standings, but the first among ORM. And this is not surprising. GreenDAO is the only ORM that fully uses code generation, compiled SQLiteStatements and other optimizations.
ActiveAndroid.Sebbia - our fork ActiveAndroid, using code generation, working through Annotation Processor, and SQLiteStatement, comes in second place among ORM and third overall. The write operation was performed almost 4 times faster than the original project, but the reading was managed to be slightly optimized.

ActiveAndroid is ranked ORM third, then comes ORMLite-ORM, which came to the Android world from the “big” Java, which has several plug-ins for working with different data sources and is quite easy to use. In last place is SugarORM - the most, in my opinion, unsuccessful of those reviewed. Firstly, the latest available version from the master branch did not support saving an array of bytes, I had to correct this misunderstanding and rebuild the library, and on the GitHub of the project there has long been a pull request that adds this function . Secondly, SugarORM creates the impression of an ActiveAndroid clone that is very severely curtailed in terms of functionality (the inability to convert objects of other classes and adapters).

Well, we figured out the performance - code generation is fast, reflection is slow. Calling SQliteDatabase.insert (...) is slower than calling a previously created SQLiteStatement. But how convenient is it to use these libraries? Let us dwell on each in more detail.

Facilities in the yard


I will begin the review of API of the presented libraries with the champion - GreenDAO .
As mentioned above, the project is rather unusual compared to other ORMs. Instead of creating classes of entities independently and specifying the fields stored in the table, GreenDAO proposes to compose these classes from other code, the resulting generation code must be run as a regular Java application. Here is how it looks like:

public static void main(String[] args) throws Exception { Schema schema = new Schema(1, "com.sebbia.ormbenchmark.greendao"); schema.enableKeepSectionsByDefault(); Entity entity = schema.addEntity("GreenDaoEntity"); entity.implementsInterface("com.sebbia.ormbenchmark.BenchmarkEntity"); entity.addIdProperty().autoincrement(); entity.addStringProperty("field1"); entity.addStringProperty("field2"); entity.addByteArrayProperty("blobArray"); entity.addDateProperty("date"); new DaoGenerator().generateAll(schema, "../src-gen/"); } 

If you need to add your own fields in the entity class, you need to place them in blocks marked with special comments:

 // KEEP FIELDS - put your custom fields here private Blob blob; // KEEP FIELDS END 

Similar comment blocks are provided for both methods and import. If you do not take into account the staggering performance of this approach, its convenience is extremely doubtful, especially if you use GreenDAO from the first day of development. However, questions arise when using the already generated code. For example, why do you need to write so much to get a DAO object:

 DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(context, "greendao", null); DaoMaster daoMaster = new DaoMaster(devOpenHelper.getWritableDatabase()); DaoSession daoSession = daoMaster.newSession(); GreenDaoEntityDao dao = daoSession.getGreenDaoEntityDao(); 

It seems to me that this is too much. It is clear that to create DaoMaster every time is not needed, but still. So, with GreenDAO you have to perform extra gestures to support the code and use the wrong API. However, in return, you get speed and nice bonuses such as supporting Protobuf objects out of the box.

Let's go to ORMLite . ORMLite proposes to actively use annotations when declaring their entities:

 @DatabaseTable(tableName = "entity") public class OrmLiteEntity implements BenchmarkEntity { @DatabaseField(columnName = "id", generatedId = true) private long id; @DatabaseField(columnName = "field1") private String field1; @DatabaseField(columnName = "field2") private String field2; @DatabaseField(columnName = "blob", dataType = DataType.BYTE_ARRAY) private byte[] blobArray; @DatabaseField(columnName = "date", dataType = DataType.DATE) private Date date; } 

Through annotations, you can specify the data type of the field, which is very convenient and does not blur the code associated with the model of the project. The project supports many types of data and storage options . For example, for java.util.Date, both numeric and string versions are provided. The disadvantages include the need to implement OrmLiteSqliteOpenHelper, through which you can get a DAO object and interact with the ORM. Using separate DAO objects eliminates the need to inherit the classes of your entities from the objects of third-party libraries and allows you to flexibly manage the cache.

ActiveAndroid uses a similar approach with annotations; however, it requires that model classes inherit from the Model class they provide. In my opinion, such a solution is optimal for convenience only if your entities are no longer inherited from any class whose parent you cannot change. Such inheritance allows you to have convenient methods like save () and delete () on model objects without creating additional DAO objects. The library also provides BigDecimal date serializers and other types, and to serialize fields of non-standard types, you only need to implement your TypeSerializer and specify it during initialization.

As mentioned above, Sugar ORM gives the impression of a rather weak ActiveAndroid clone. However, Sugar ORM does not require inheritance from any abstract class and has a rather concise API:

 @Override public void saveEntitiesInTransaction(final List<SugarOrmEntity> entities) { SugarRecord.saveInTx(entities); } @Override public List<SugarOrmEntity> loadEntities() { List<SugarOrmEntity> entities = SugarRecord.listAll(SugarOrmEntity.class); return entities; } 

ActiveAndroid.Sebbia is a fork of ActiveAndroid with support for code generation. In this project, the generation of the SQLiteStatement and Cursor binding code with the entity object is performed using the Annotation Processor. Using Annotation Processor instead of a plug-in for IDE allows you to use it both in Eclipse and IntelliJ IDEA, and when building a project using Gradle or Ant. However, this imposes some restrictions on the visibility of the model class fields: the minimum allowable visibility in this case will be without a modifier (default). Code generation allowed to achieve about 30% performance gain, all the rest is due to the pre-compiled SQLiteStatement. Also, this fork contains OneToManyRelation, ManyToManyRelation, and support for automatic migrations, which is used when the SQL migration script for the current version is not found.

Conclusion


In conclusion, I would like to summarize our little research. ORM is a useful tool that helps you save time when developing your applications. And it is absolutely indispensable in the case of a model with a multitude of entities and interconnections between them.
It is worth remembering that in real life, the end user most likely will not see any difference between the fastest and the slowest ORM, so whether to think about this is the choice of everyone. It remains to add that the solution you choose should be convenient and meet your requirements. In any case, you should follow the general rules when choosing open-source libraries in your projects, namely, to evaluate which known applications use, what quality of the source code and how it works from the inside.

Repository links:
Project benchmark
ActiveAndroid.Sebbia

Bibliography

Source: https://habr.com/ru/post/245299/


All Articles