An ORM for Android with type-safety and painless smart migrations

Overview

Android Orma Circle CI Download Gitter backers

Android Orma

Orma is a ORM (Object-Relation Mapper) for Android SQLiteDatabase. Because it generates helper classes at compile time with annotation processing, its query builders are type-safe.

The interface of Orma is simple and easy to use, as the author respects the Larry Wall's wisdom:

Easy things should be easy, and hard things should be possible -- Larry Wall

Table of Contents

Motivation

There are already a lot of ORMs for Android. Why I have to add another wheel?

The answer is that I need an ORM that has all the following features:

  • Fast as hand-written code
  • POJO models
    • Model classes should have no restriction
    • Might implement Parcelable and/or extend any classes
    • They should be passed to another thread
  • A database handle must be an object instance
    • Not a static-method based class
    • Even though it is designed to be used as a singleton scope
  • Easy migration
    • Some ALTER TABLE, e.g. add column and drop column, should be detected and processed
    • There is a wheel in Perl: SQL::Translator::Diff
  • Type safe and code completion friendly
    • db.selectFromModel() is better than new Select(Model.class)
    • todos.idEq(id).toList() is better than todos.equalTo("id", id)
  • Custom raw queries are sometimes inevitable
    • GROUP BY ... HAVING ...
    • SELECT max(value), min(value), avg(value), count(value) FROM ...

And now they are exactly what Orma has.

Requirements

  • JDK 8 (1.8.0_66 or later) to build
  • Android API level 15 to use

Getting Started

Declare dependencies to use Orma and its annotation processor.

dependencies {
    annotationProcessor 'com.github.maskarade.android.orma:orma-processor:6.0.2'
    compile 'com.github.maskarade.android.orma:orma:6.0.2'
}

NOTE: if you use Android Gradle Plugin before 2.2.0, you must use android-apt plugin instead of annotationProcessor configuration.

Synopsis

First, define model classes annotated with @Table, @Column, and @PrimaryKey and run the Build APK command to generate helper classes.

package com.github.gfx.android.orma.example;

import com.github.gfx.android.orma.annotation.Column;
import com.github.gfx.android.orma.annotation.PrimaryKey;
import com.github.gfx.android.orma.annotation.Table;

import android.support.annotation.Nullable;

@Table
public class Todo {

    @PrimaryKey(autoincrement = true)
    public long id;

    @Column(indexed = true)
    public String title;

    @Column
    @Nullable // allows NULL (default: NOT NULL)
    public String content;

    @Column
    public long createdTimeMillis;
}

Second, instantiate a database handle OrmaDatabase, which is generated by orma-processor.

Here is an example to configure OrmaDatabase:

// See OrmaDatabaseBuilderBase for other options.
OrmaDatabase orma = OrmaDatabase.builder(context)
    .name("main.db") // default: "${applicationId}.orma.db"
    .build();

Then, you can create, read, update and delete models via OrmaDatabase:

Todo todo = ...;

// create
orma.insertIntoTodo(todo);

// prepared statements with transaction
orma.transactionSync( -> { // or transactionAsync() to execute tasks in background
    Inserter<Todo> inserter = orma.prepareInsertIntoTodo();
    inserter.execute(todo);
});

// read
orma.selectFromTodo()
  .titleEq("foo") // equivalent to `where("title = ?", "foo")`
  .executeAsObservable() // first-class RxJava interface
  .subscribe(...);

// update
orma.updateTodo()
  .titleEq("foo")
  .content("a new content") // to setup what are updated
  .execute();

// delete
orma.deleteFromTodo()
  .titleEq("foo")
  .execute();

The Components

Database Handles

A database handle, named OrmaDatabase by default, is generated by orma-processor, which is an entry point of all the high-level database operations.

This is typically used as a singleton instance and you don't need to manage its lifecycle. That is, you don't need to explicitly close it.

Models

A model in Orma is a Java class that is annotated with @Table, which has at least one column, a field annotated with @Column or @PrimaryKey.

orma-processor generates helper classes for each model: Schema, Relation, Selector, Updater, and Deleter.

Because these helper classes are generated at the compile time, you can use Orma as a type-safe ORM.

Schema Helpers

A Schema helper, e.g. Todo_Schema, has metadata for the corresponding model.

This is an internal helper class and not intended to be employed by users.

Relation Helpers

A Relation helper, e.g. Todo_Relation, is an entry point of table operations.

This is created by a database handle:

public static Todo_Relation relation() {
  return orma.relationOfTodo();
}

And is able to create Selector, Updater, Deleter, and Inserter for the target model.

Todo_Relation todos = orma.relationOfTodo();

todos.selector().toList(); // Todo_Selector
todos.updater().content("foo").execute(); // Todo_Updater
todos.inserter().execute(todo); // Inserter<Todo>
todos.deleter().execute(); // Todo_Deleter

This can be a subset of a table which has ORDER BY clauses and WHERE clauses with some List-like methods:

Todo_Relation todos = orma.relationOfTodo()
  .doneEq(false) // can have conditions
  .orderByCreatedTimeMillis(); // can have orders

// List-like features:
int count = todos.count();
Todo todo = todos.get(0);

// Convenience utilities
int position = todos.indexOf(todo);
todos.deleteWithTransactionAsObservable()
  .subscribeOn(Schedulers.io())
  .observeOn(AndroidSchedulers.mainThread())
  .subscribe(position -> {
    notifyItemRemoved(position); // assumes Adapter#notifyItemRemoved()
  })
todos.truncateWithTransactionAsObservable()
  .subscribeOn(Schedulers.io())
  .subscribe();

// Todo_Relation implements Iterable<Todo>
for (Todo todo : todos) {
  // ...
}

And has convenience #upsert() to "save it anyway", returning a new model:

Todo_Relation todos = orma.relationOfTodo()

Todo newTodo = todos.upsert(todo); // INSERT if it's not persistent; UPDATE Otherwise

Unlike INSERT with OnConflict.REPLACE, #upsert() doesn't break associations.

NOTE: if you use a model after #upsert(), you must use the returned newModel. This is because Orma does not change the model's primary key on INSERT.

Selector Helpers

A Selector helper, e.g. Todo_Selector, is created by a Relation:

Todo_Selector selector = relation().selector();
// or orma.selectFromTodo();

This is a query builder for SELECT ... FROM * statements.

Updater Helpers

An Updater helper, e.g. Todo_Updater, is created by a Relation:

Todo_Updater updater = relation().updater();
// or orma.updateTodo();

This is a query builder for UPDATE * statements.

Deleter Helpers

A Deleter helper, e.g. Todo_Deleter, is created by a Relation:

Todo_Deleter deleter = relation().deleter();
// or orma.deleteFromTodo();

This is a query builder for DELETE FROM * statements.

Query Helper Methods

There are Query Helpers which are generated to query conditions and orders in a type-safe way.

For example, titleEq() shown in the synopsis section, are generated to help make WHERE and ORDER BY clauses, for Relation, Selector, Deleter, and Updater.

Most of them are generated for columns with indexed = true, and some are for @PrimaryKey columns.

List of Query Helper Methods

Here is a list of Query Helpers that are generated for all the indexed columns, where * is a column name pladeholder:

Method SQL
*Eq(value) * = value
*NotEq(value) * <> value
*In(values) * IN (values)
*NotIn(values) * NOT IN (values)

The following are generated for @Nullable columns.

Method SQL
*IsNull() * IS NULL
*IsNotNull() * IS NOT NULL

The following are generated for numeric columns (i.e. byte, short, int, long, float, double, and their corresponding box types)

Method SQL
*Lt(value) * < value
*Le(values) * <= value
*Gt(value) * > value
*Ge(value) * >= value
*Between(a, b) * BETWEEN a AND b

The following are generated for TEXT and not PRIMARY KEY columns.

Method SQL
*Glob(pattern) * GLOB pattern
*NotGlob(pattern) * NOT GLOB pattern
*Like(pattern) * LIKE pattern
*NotLike(pattern) * NOT LIKE pattern

And ORDER BY helpers:

Method SQL
orderBy*Asc() ORDER BY * ASC
orderBy*Desc() ORDER BY * DESC

How to Control Generation of Query Helpers

This is an advanced setting for those who know what they do.

You can control which Query Helpers are generated for a column by @Column(helpers = ...) attribute:

@Column(
    helpers = Column.Helpers.AUTO // default to AUTO
)

Here are the definition of options defined in Column.java:

long AUTO = -1; // the default, a smart way
long NONE = 0;

long CONDITION_EQ = 0b01;
long CONDITION_NOT_EQ = CONDITION_EQ << 1;
long CONDITION_IS_NULL = CONDITION_NOT_EQ << 1;
long CONDITION_IS_NOT_NULL = CONDITION_IS_NULL << 1;
long CONDITION_IN = CONDITION_IS_NOT_NULL << 1;
long CONDITION_NOT_IN = CONDITION_IN << 1;

long CONDITION_LT = CONDITION_NOT_IN << 1;
long CONDITION_LE = CONDITION_LT << 1;
long CONDITION_GT = CONDITION_LE << 1;
long CONDITION_GE = CONDITION_GT << 1;
long CONDITION_BETWEEN = CONDITION_GE << 1;

long CONDITIONS = CONDITION_EQ | CONDITION_NOT_EQ | CONDITION_IS_NULL | CONDITION_IS_NOT_NULL
        | CONDITION_IN | CONDITION_NOT_IN
        | CONDITION_LT | CONDITION_LE | CONDITION_GT | CONDITION_GE | CONDITION_BETWEEN;

long ORDER_IN_ASC = CONDITION_BETWEEN << 1;
long ORDER_IN_DESC = ORDER_IN_ASC << 1;

long ORDERS = ORDER_IN_ASC | ORDER_IN_DESC;

long ALL = CONDITIONS | ORDERS;

The Inserter Helpers

This is a prepared statement for INSERT INTO ... for bulk insertions.

Inserter<Todo> inserter = relation().inserter();
// or orma.insertIntoTodo()

inserter.execute(todo);
inserter.executeAll(todos);

Details of Database Handles

The section describes the details of database handles.

Configuration of Database Handles

The database class is configured by the @Database annotation:

@Database(
    databaseClassName = "OrmaDatabase", // default to "OrmaDatabase"
    includes = { /* ... */ } // Give model classes to handle
    excludes = { /* ... */ } // Give model classes not to handle
)
public class DatabaseConfiguration { }

The annotated class is not used for now, but the package is used to place the OrmaDatabase class.

Database Handle Builders

OrmaDatabase.builder(Context) returns a builder isntance, which has configure the database handle instance:

Method Description Default
name(String) The filename of SQLite DB "${package}.orma.db"
migrationEngine(MigrationEngine) Custom migration engine OrmaMigration
writeAheadLogging(boolean) SQLite WAL flag true
foreignKeys(boolean) SQLite FOREIGN_KEYS flag true
migrationStep(int, ManualStepMigration.Step) A migration step none
trace(boolean) Output executed queries to logcat if true dynamic (*1)
readOnMainThread(AccessThreadConstraint) Check read operation on main thread dynamic (*2)
writeOnMainThread(AccessThreadConstraint) Check write operation on main thread dynaimc (*3)
  • *1 BuildConfig.DEBUG ? true : false
  • *2 BuildConfig.DEBUG ? WARN : NONE
  • *3 BuildConfig.DEBUG ? FATAL : NONE

Note that Orma aborts if writing occurs on main thread in debug build.

Use background threads, e.g. via AsyncTask for writing, or RxJava interfaces with Schedulers.io().

Otherwise you can disable this behavior:

OrmaDatabase orma = OrmaDatabase.builder(context)
    .writeOnMainThread(AccessThreadConstraint.NONE)
    .build();

In-Memory Database

You can create in-memory databases by passing null to OrmaDatabase.Builder#name().

This is useful for testing.

Details of Models

The section describes the details of model definition.

Setters and Getters

Orma can use getters and setters if columns have corresponding methods.

You can also connect getters and setters with @Getter and @Setter respectively, which tells orma-processor to use accessors.

Each accessor name can have a column name in SQLite databases, which is inferred from its method name if omitted.

@Table
public class KeyValuePair {

    static final String kKey = "Key";

    @Column(kKey) // specifies the name
    private String key;

    @Column // omits the name
    private String value;

    @Getter(kKey)
    public String getKey() {
        return key;
    }

    @Setter(kKey)
    public void setKey(String key) {
        this.key = key;
    }

    // used as a getter for the "value" column
    // @Getter is optional in this case
    public String getValue() {
        return value;
    }

    // used as a setter for the "value" column
    // @Setter is optional in this case
    public void setValue(String value) {
        this.value = value;
    }
}

Immutable Models

Immutable models, where all the fields are declared with final, are supported by annotating a constructor with @Setter.

@Table
public class KeyValuePair {

    @Column
    public final String key;

    @Column
    public final String value;

    @Setter
    KeyValuePair(String key, String value) {
        this.key = key;
        this.value = value;
    }
}

It can be declared with custom names:

@Table
public class KeyValuePair {
    static final String kKey = "Key";
    static final String kValue = "Value";

    @Column(kKey)
    public final String key;

    @Column(kValue)
    public final String value;

    KeyValuePair(@Setter(kKey) String key, @Setter(kValue) String value) {
        this.key = key;
        this.value = value;
    }
}

Composite Indexes

There is the indexes parameter that @Table takes in order to create composite indexes (a.k.a. multi-column indexes).

// for CREATE INDEX:
@Table(indexes = @Index(value = {"resourceType", "resourceId"}))
public class Entry {

    @PrimaryKey
    public long id;

    @Column
    public String resourceType;

    @Column
    public long resourceId;
}
// for CREATE UNIQUE INDEX:
@Table(
    indexes = @Index(
                value = {"resourceType", "resourceId"},
                unique = true
        )
)
public class Entry {

    @PrimaryKey
    public long id;

    @Column
    public String resourceType;

    @Column
    public long resourceId;
}

Composite indexes generate query helper methods only for == and ORDER BY for helper classes like the following:

  • Selector#resourceTypeAndResourceIdEq(String, long)
  • Selector#orderByResourceTypeAndResourceIdAsc()
  • Selector#orderByResourceTypeAndResourceIdDesc()

You can control generated helpers with the helpers parameter.

See also Query Helper Methods.

Reserved Names

Column names starting $ are reserved for metadata.

Other names, including SQLite keywords, are allowed.

RxJava Integration

RxJava integration provides a set of powerful API to transform, filter, and combine DB rows.

For example, there is a model named Book with @Column(unique = true) String title and you'd like to get a Map<String, Book> where the key is Book#title:

Map<String, Book> map = db.selectFromBook()
    .executeAsObservable()
        .toMap(new Function<Book, String>() {
            @Override
            public String apply(Book book) throws Exception {
                return book.title;
            }
        }).blockingGet();

Associations

Two or more Orma models can be associated with association mechanism.

There are two type of associations: has-one and has-many.

In addition, there are another two kind of association supports: indirect associations with SingleAssociation<T> and direct associations.

Has-One Associations with SingleAssociation<T>

There is SingleAssociation<T> to support has-one associations, which is retrieved on demand; this is useful if the associations are not always used.

For example, a book has a publisher:

@Table
class Publisher {
  @PrimaryKey
  public long id;
}

@Table
class Book {

    @Column
    public SingleAssociation<Publisher> publisher;
}

To save this a book:

Book book = new Book();
Publisher publisher = new Publisher();

long publisherId = db.insertIntoPublisher()
    .execute(publisher);

// if publisher has a valid primary key, `just(publisher)` is also okay.
book.publisher = SingleAssociation.just(publisherId);

db.insertIntoBook()
    .execute(book)

To get a publisher from books:

db.selectFromBook()
    .forEach((book) -> {
        // blocking:
        Publisher publisher = book.publisher.get();

        // with RxJava Single<Publisher>:
        book.publisher.single()
            .subscribe((publisher) -> {
                // use publisher
            })
    });

Direct Associations

There are direct associations, where an Orma model has another Orma model directly.

Given a has-one association, Book has-one Publisher:

@Table
class Publisher {
  @PrimaryKey
  public long id;

  @Column
  public String name;
}

@Table
class Book {

    @PrimaryKey
    public long id;

    @column
    public String title;

    @Column
    public Publisher publisher;
}

The corresponding table definition is something like this:

CREATE TABLE `Publisher` (
  `id` INTEGER PRIMARY KEY,
  `name` TEXT NOT NULL
)
CREATE TABLE `Book` (
  `id` INTEGER PRIMARY KEY,
  `title` TEXT NOT NULL,
  `publisher` INTEGER NOT NULL
    REFERENCES `Publisher`(`id`) ON UPDATE CASCADE ON DELETE CASCADE
)

In SQL, Book#publisher refers Publisher#id, indicating the two tables should be joined in SELECT statements.

In Java, Book#publisher is a Publisher instance, which is retrieved in each SELECT operations. There is no lazy loading in direct associations.

Has-Many Associations with SingleAssociation<T>

Has-many associations are not directly supported but you can define a method to get associated objects:

@Table
class Publisher {
    @PrimaryKey
    public long id;

    // NOTE: If OrmaDatabase is a singleton, no parameter is required!
    public Book_Relation getBooks(OrmaDatabase db) {
        return db.relationOfBook().publisherEq(this);
    }
}

@Table
class Book {

    @Column(indexed = true)
    public SingleAssociation<Publisher> publisher;

}

Has-Many Associations with Direct Associations

As SingleAssociation is, you can define a helper method to get has-many associations:

@Table
class Publisher {
    @PrimaryKey
    public long id;

    // NOTE: If OrmaDatabase is a singleton, no parameter is required!
    public Book_Relation getBooks(OrmaDatabase db) {
        return db.relationOfBook().publisherEq(this);
    }
}

@Table
class Book {

    @Column(indexed = true)
    public Publisher publisher;

}

Limitations in Associations

  • There are no methods to query associated models

These issues will be fixed in a future.

Type Adapters

Orma models are able to have embedded objects with type adapters, a.k.a. static type adapters, by defining classes with @StaticTypeAdapter annotation.

For example, if you want to embed LatLng in your Orma model, you can define a type adapter like this:

@StaticTypeAdapter(
    targetType = LatLng.class, // required
    serializedType = String.class // required
)
public class LatLngAdapter {

    // SerializedType serialize(TargetType source)
    @NonNull
    public static String serialize(@NonNull LatLng source) {
        return source.latitude + "," + source.longitude
    }

    // TargetType deserialize(SerializedType serialized)
    @NonNull
    public static LatLng deserialize(@NonNull String serialized) {
        String[] values = serialized.split(",");
        return new LatLng(
            Double.parseDouble(values[0]),
            Double.parseDouble(values[1]));
    }
}

@StaticTypeAdapter requires targetType and serializedType options and two static methods SerializedType serialize(TargetType) and TargetType deserialize(SerializedType).

How Serialized Types Used

A @StaticTypeAdapter#serializedType is bound to an SQLite storage type. Thus it must be one of the "Java Type" listed the table below, where each "Java Type" has a corresponding "SQLite Type":

Java Type SQLite Type
int INTEGER
short INTEGER
long INTEGER
boolean INTEGER
float REAL
double REAL
String TEXT
byte[] BLOB

@StaticTypeAdapters for Multiple Serializers at Once

You can also define multiple type serializers to single class with @StaticTypeAdapters annotation containers:

@StaticTypeAdapters({
    @StaticTypeAdapter(
        targetType = MutableInt.class,
        serializedType = int.class,
        serializer = "serializeMutableInt",
        deserializer = "deserializeMutableInt"
    ),
    @StaticTypeAdapter(
        targetType = MutableLong.class,
        serializedType = long.class,
        serializer = "serializeMutableLong",
        deserializer = "deserializeMutableLong"
    )
})
public class TypeAdapters {

    public static int serializeMutableInt(@NonNull MutableInt target) {
        return target.value;
    }

    @NonNull
    public static MutableInt deserializeMutableInt(int deserialized) {
        return new MutableInt(deserialized);
    }

    public static long serializeMutableLong(@NonNull MutableLong target) {
        return target.value;
    }

    @NonNull
    public static MutableLong deserializeMutableLong(long deserialized) {
        return new MutableLong(deserialized);
    }
}

Built-In Type Adapters

There are built-in type adapters for typically used value objects and collections:

  • java.math.BigDecimal
  • java.math.BigInteger
  • java.nio.ByteBuffer
  • java.util.Currency
  • java.util.Date
  • java.sql.Date
  • java.sql.Time
  • java.sql.Timestamp
  • java.util.UUID
  • java.util.List<String>
  • java.util.ArrayList<String>
  • java.util.Set<String>
  • java.util.HashSet<String>
  • android.net.Uri

Generic Type Adapters

If your deserialize() takes a Class<T> parameter, the type serializer is generic, handling classes with the common base classe.

For example, if you have some enums that implement EnumDescription, e.g. T extends Enum<T> & EnumDescription, you can handle it with a generic type adapter.

Given an interface EnumDescription:

public interface EnumDescription {

    long getValue();
}

And here is its type adapter:

@StaticTypeAdapter(
        targetType = EnumDescription.class,
        serializedType = long.class
)
public class EnumTypeAdapter {

    public static <T extends Enum<T> & EnumDescription> long serialize(@NonNull T value) {
        return value.getValue();
    }

    @NonNull
    public static <T extends Enum<T> & EnumDescription> T deserialize(long serialized, @NonNull Class<T> type) {

        for (T enumValue : type.getEnumConstants()) {
            if (enumValue.getValue() == serialized) {
                return enumValue;
            }
        }

        throw new RuntimeException("Unknown id: " + serialized + " for " + type);
    }
}

Now deserialize() uses the type information for the conclete target class.

Pagination

There are two style pagination. You can use either, but not mixed.

limit and offset

SQL style pagination:

for (Todo todo : orma.selectFromTodo().titleEq("buy").offset(0).limit(10)) {
    // ...
}

page and per

"paging" style pagination inspired from Ruby's kaminari.

for (Todo todo : orma.selectFromTodo().titleEq("buy").page(1).per(10)) {
    // ...
}

Note that page starts from 1.

Raw Queries

For low-level operations, e.g. executing a raw query, you can use OrmaDatabase#getConnection(), which returns OrmaConnection.

For example of #rawQuery:

Cursor cursor = db.getConnection().rawQuery("SELECT max(bookId) as max_id, min(bookId) as min_id FROM Book");
cursor.moveToFirst();
// ...get data from cursor...
cursor.close();

Or, you can use #execSQL for mutations:

OrmaConnection conn = db.getConnection();
conn.execSQL("VACUUM");
conn.execSQL("ANALYZE");

NOTE: Don't use #rawQuery() for performance because Orma query builders are fast enough.

Migration

There is a pluggable migration mechanism via the MigrationEngine interface.

The default migration engine is SchemaDiffMigration, which handles schema changes by making diff with old and new DDL stored in sqlite_master. That is, you don't need migration steps for the following cases:

  • Adding tables
  • Adding columns (In this case, you need to add defaultExpr or @Nullable to new columns for auto-migration to work)
  • Changing column types
  • Changing column constraints (NOT NULL, UNIQUE, and etc.)

You can also define migration steps for each schema version, which uses applications version code (i.e. BuildConfig.VERSION) as schema versions.

Here is an example to define migration steps:

int VERSION_2; // a past version of application's VERSION_CODE

OrmaDatabase orma = OrmaDatabase.builder(this)
        .migrationStep(VERSION_2, new ManualStepMigration.ChangeStep() {
            @Override
            public void change(@NonNull ManualStepMigration.Helper helper) {
              Log.(TAG, helper.upgrade ? "upgrade" : "downgrade");
              helper.execSQL("DROP TABLE foo");
              helper.execSQL("DROP TABLE bar");
            }
        })
        // ... other configurations
        .build();

See migration/README.md for details.

DataSet Changed Events

NOTE: This is experimental in v4.2.5: its existence, signature or behavior might change without warning from one release to the next.

Relation#createQueryObservable() can create a event stream to observe data-set changed events for the relation.

This likes SQLBrite's' "Query Observable", whereas Orma's does not notify the initial event.

// NOTE: Keep the observable instance. If it's released, the observable is disposed.

// create a query observable, which is a hot observable
Observable<Author_Selector> observable = db.relationOfAuthor()
        .createQueryObservable();

// subscribe the events
observable.flatMap(new Function<Author_Selector, Observable<Author>>() {
    @Override
    public Observable<Author> apply(Author_Selector selector) throws Exception {
        Log.d(TAG, "Author has been changed!");
        return selector.executeAsObservable();
    }
})
        .map(new Function<Author, String>() {
            @Override
            public String apply(Author author) throws Exception {
                return author.name;
            }
        })
        .subscribe(new Consumer<String>() {
            @Override
            public void accept(String name) throws Exception {
                Log.d(TAG, "name: " + name);
            }
        });

See OrmaListAdapter and OrmaRecyclerViewAdapter, which use Query Observables to trigger #notifyDataSetChanged().

Cooperation with Serialization Libraries

Beause Orma reuqires nothing to do to models, serializers, e.g. Android Parcels or GSON, can serialize Orma models.

Encryption

There's an encryption extension as orma-encryption since Orma v5.0.0-rc1:

dependencies {
    compile 'com.github.maskarade.android.orma:orma-encryption:6.0.2'
}

That provies EncryptedDatabase:

String password = "...";
OrmaDatabase orma = OrmaDatabase.builder(context)
    .provider(new EncryptedDatabase.Provider(password))
    // ...
    .build();

Encrypted databases are managed by SQLCipher, so the database files are not compatible with non-encrypted ones.

Note that with this extension the database handle throws net.sqlcipher.database.SQLException instead of android.database.SQLException as runtime exceptions, so it might not be 100% compatible with the default database.

Example

There is an example app to demonstrate:

  • Migration
  • Orma with RecyclerView / ListView
  • Benchmark (see below)

Benchmark

There is a simple benchmark with Realm and hand-written SQLiteDatabase code:

example/BenchmarkFragment

Here is a result performed on Android 6.0.2 / Xperia Z4 as of Orma v4.2.5 and Realm 2.3.0, processing 10 items x 100 times:

I welcome benchmark in another condition and/or another code.

FAQ

How can I enable debug logging on release build?

Call OrmaDatabase.Builder#trace(boolean) with true:

OrmaDatabase orma = OrmaDatabase.builder(context)
    .trace(true)
    .create();

This option also enables logging in the default migration engine.

If you give a custom migration engine to the orma builder, you have to enable trace flag to its constructor:

boolean trace = true;
SchemaDiffMigration migration = new SchemaDiffMigration(context, trace);

How can see the generated Java files?

As other annotation processors do, Orma save files to $modle/build/generated/source/apt/.

You can see generated files for example models.

Does Orma work with Kotlin?

Yes, but it's experimental. Here is an example to use Orma with Kotlin:

https://github.com/gfx/OrmaWithKotlin

NOTE: Kotlin APT support, a.k.a. kapt, is really unstable. Don't ask me how to solve kapt problems.

When the database handle is opened and closed?

Orma opens the database handle in instantiating OrmaDatabase, and you don't need to close it.

In other word, you can define the database handle as a singleton instance in your application scope, and forget close.

Who uses Orma?

Here is a list of open-source Androdi apps using Orma which are released to Google Play:

Or you can search use cases by GitHub search.

Also, here is a list of apps using Orma which are proprietary:

Tell me if your projects use Orma!

Support

Licenses in Runtime Dependencies

Contribution

Patches are welcome!

Release Engineering for Maintainers

Artifact Repository

https://bintray.com/orma

What you do

./gradlew bumpMajor # or bumpMinor / bumpPatch
code CHANGES.md # edit change logs
git add -va
make publish # run tests, build artifacts, publish to jcenter, and make a git tag to HEAD

Note that you have to edit VERSION file by hand if you'd like to release release candidates.

See Makefile for details.

Documentation Tools

Visual Studio Code (a.k.a. vscode) is recommended to edit README.md and CHANGELOG.md. Especially the ToC section is managed by AlanWalk/Markdown-TOC.

See Also

Authors and Contributors

FUJI Goro (gfx).

And contributors are listed here: Contributors

If you are interested in sponsoring this project, it is really welcome, as I'll spend more time to develop this software: https://opencollective.com/android-orma

License

Copyright (c) 2015 FUJI Goro (gfx).

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Comments
  • Add encryption support

    Add encryption support

    Can Orma get encryption support in the future? Integrate protect can get best experience for Orma (such as Realm, that support encryption on low level)

    Lib for sql encryption https://github.com/sqlcipher/android-database-sqlcipher

    enhancement 
    opened by ablack13 18
  • Cannot build Example.

    Cannot build Example.

    There aren't the following classes in exaple directory. com.github.gfx.android.orma.example.databinding.ActivityTodoBinding com.github.gfx.android.orma.example.databinding.CardTodoBinding com.github.gfx.android.orma.example.orma.OrmaDatabase

    question 
    opened by suzukaze 14
  • Replace novoda/bintray-release with bintray/gradle-bintray-plugin

    Replace novoda/bintray-release with bintray/gradle-bintray-plugin

    Replace novoda/bintray-release with bintray official bintray/gradle-bintray-plugin to get rid of blocker of #448.

    I can't test upload because I don't know the credentials, but you can check generated artifacts and pom.

    Execute

    ./gradlew clean install
    

    And please verify the files at $HOME/.m2/repository/com/github/maskarade/android/orma uploaded to bintray.

    opened by chibatching 10
  • "db.selectFromModel() is better than new Select(Model.class)"

    I'm considering migrating from ActiveAndroid to Orma, but to me, the "code completion" feature seems like a deal breaker. The reason for that, is that I'd like to be able to pass the queried table as an argument for genericness purposes. I didn't have time to look at the code yet, but is there any "db.selectFrom(Model.class)" method available? If not, would you consider adding it?

    question 
    opened by PtiPingouin 10
  • Make RxJava optional

    Make RxJava optional

    Resolve #396

    Currently, RxJava is used as default. When developers want to opt-out RxJava, write configuration as:

    @GenerationOption(rxJavaSupport = false)
    public class OrmaGenerationOption { }
    

    Sorry for this PR looks big, however the changes are pretty simple. I extracted RxJava helper methods from some classes into new "Rx*" classes listed below:

    |without Rx methods|with Rx methods| |---|---| |OrmaConnection|RxOrmaConnection| |Relation|RxRelation| |Inserter|RxInserter| |Selector|RxSelector| |Updater|RxUpdater| |Deleter|RxDeleter| |SingleAssociation|RxSingleAssociation| |DataSetChangedTrigger|RxDatasetChangedTrigger|

    Orma processor generates classes to use "Rx*" classes instead of base one when GenerationOption(rxJavaSupport = true).

    Most of changes are actually "code move" but some are newly written by me, so I'm going to comment into new code pieces (it's review points).

    opened by k-kagurazaka 9
  • Application did not close the cursor or database object that was opened here (net.sqlcipher.database.DatabaseObjectNotClosedException)

    Application did not close the cursor or database object that was opened here (net.sqlcipher.database.DatabaseObjectNotClosedException)

    i am getting these with 5.0.0-rc1 with encrypted database when i exit my app

    ###/com.zoffcc.applications.trifa E/Database: close() was never explicitly called on database '/data/user/0/xxxxxx/xxxx.db' 
    net.sqlcipher.database.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
        at net.sqlcipher.database.SQLiteDatabase.<init>(SQLiteDatabase.java:2402)
        at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1148)
        at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1203)
        at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1198)
        at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1183)
        at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1219)
        at com.github.gfx.android.orma.encryption.EncryptedDatabase$Provider.provideOnDiskDatabase(EncryptedDatabase.java:170)
        at com.github.gfx.android.orma.OrmaConnection.openDatabase(OrmaConnection.java:106)
        at com.github.gfx.android.orma.OrmaConnection.<init>(OrmaConnection.java:96)
        at com.zoffcc.applications.trifa.OrmaDatabase$Builder.build(OrmaDatabase.java:466)
        at com.zoffcc.applications.trifa.MainActivity.onCreate(MainActivity.java:395)
        at android.app.Activity.performCreate(Activity.java:6681)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2651)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2765)
        at android.app.ActivityThread.-wrap12(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1506)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:153)
        at android.app.ActivityThread.main(ActivityThread.java:6234)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:778)
    
    wontfix sqlcipher 
    opened by zoff99 8
  • OrmaListAdapter::notifyDatasetChanged() should update current model when DB data is updated.

    OrmaListAdapter::notifyDatasetChanged() should update current model when DB data is updated.

    repro step

    1. Show some items with ListView in ActivityA
    2. edit some model and commit it in another ActivityB (like standard master-detail structure).
    3. Then go back to ActivityA and call notifyDataSetChanged()

    expected

    the updated contents is shown

    actual

    The old content of model remains shown.

    further info

    I think cache of OrmaAdapter should be invalidated when OrmaListAdapter::notifyDataSetChanged() called. Or may be I misunderstand very basic of these classes...

    here is my work around:

    abstract class RelodableOrmaListAdapter<Model> extends OrmaListAdapter<Model> {
        public RelodableOrmaListAdapter(@NonNull Context context, @NonNull Relation<Model, ?> relation) {
            super(new ReloadableOrmaAdapter<Model>(context, relation));
        }
    
        public void reload() {
            ((ReloadableOrmaAdapter) this.delegate).clearCache();
            notifyDataSetChanged();
        }
    }
    
    public class ReloadableOrmaAdapter<Model> extends OrmaAdapter {
    
        public ReloadableOrmaAdapter(@NonNull Context context, @NonNull Relation relation) {
            super(context, relation);
        }
    
        public void clearCache() {
            cache.evictAll();
        }
    }
    
    opened by karino2 8
  • Models can't have multiple models with the same type

    Models can't have multiple models with the same type

    We can't do (as of v2.0.3):

    
    @Table class Foo { 
      @PrimaryKey long id;
    }
    
    @Table class Bar {
      @Column public Foo foo1;
      @Column public Foo foo2;
    }
    

    This is difficult because fully-qualified names (e.g. Foo.id) are no longer reliable. Needs the table-alias allocator.

    bug 
    opened by gfx 8
  • Fix #436: ManualStepMigration not working

    Fix #436: ManualStepMigration not working

    Fix #436

    There is a bug that the DB version (user_version) is not updated when the manual step migrations do not call execSQL (or wrappers of it).

    To fix it, always update DB version just like as when migration steps are empty.


    As @k-kagurazaka suggested in https://github.com/maskarade/Android-Orma/pull/469#issuecomment-433769182, there as another problem in #436 ... the default migration engine is SchemaDiffMigration.

    In this PR (esp. at c36a3c7), Orma always uses OrmaMigraion to correctly start ManualStepMigration.

    opened by gfx 7
  • Encryption support by SQLCipher

    Encryption support by SQLCipher

    I added encryption module to support encryption by SQLCipher.

    I made SQLiteDatabase and SQLiteStatement abstract, and added features that allow us to switch abstract Database. This approach is the almost same as GreenDao's one.

    Would you like to support encryption? If yes, I want to continue contributions for this feature after discussing about:

    • Testing approach (There are no tests now.)
    • Abstraction design (Currently, abstraction exists in migration module, so this is not smart.)

    Thanks.

    opened by k-kagurazaka 7
  • Null nested models are instantiated (with null fields).

    Null nested models are instantiated (with null fields).

    Consider the following code, where Model2 is also a table:

    @Table
    public class Model1 {
        @Nullable
        protected Model2 model2;
    }
    

    When reading from database an instance of Model1 that has no model2, model1 still gets a model2 instance, with null values.

    In Model1_Schema, I can see there are no condition for instantiating model2:

      @NonNull
      @Override
      public Model1 newModelFromCursor(@NonNull OrmaConnection conn, @NonNull Cursor cursor, int offset) {
        Model1 model = new Model1();
        model.setModel2(Model2_Schema.INSTANCE.newModelFromCursor(...));
        return model;
      }
    

    Model2_Schema.INSTANCE.newModelFromCursor() is called no matter what.

    Am I missing something?

    opened by PtiPingouin 7
  • Migrate from JCenter() to the mavenCentral()

    Migrate from JCenter() to the mavenCentral()

    Description

    As of 10/26/222 the orma library was removed from Jcenter because it has not been updated in the last 2 years. (Maven Central repo also have a reference to the JCenter )

    So , orma library could not be downloaded with gradle , we get timeout error

    Solution

    I found the solution how we can implement orma project with gradle

     repositories {
             jCenter() -> remove this line
            mavenCentral()
            gradlePluginPortal()
        }
    

    Reference

    https://jcenter.bintray.com/com/github/maskarade/android/orma/orma/6.0.2 -> Could not found

    opened by rustam-baktybek 13
  • Could you please release next version for sqlcipher update?

    Could you please release next version for sqlcipher update?

    I noticed sqlcipher update was merged, but next version was not released yet. https://github.com/maskarade/Android-Orma/pull/480 Could you please release next version with migration guide for sqlcipher 4.x?

    opened by phicdy 1
  • Into the Sunset on May 1st: Bintray, JCenter, GoCenter, and ChartCenter

    Into the Sunset on May 1st: Bintray, JCenter, GoCenter, and ChartCenter

    https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/

    can you migrate the artifacts to the new service before bintray shuts down?

    help wanted 
    opened by zoff99 3
  • Request For New Maintainers

    Request For New Maintainers

    Hello, Orma users,

    I believe this project is worth maintaining even if Room gets a de-facto standard ORM because Orma's smart migration and type-safe helpers are still useful enough.

    However, I, the creator of Orma, have no time to maintain this project because my life has changed in the last several years.

    So now I'm finding new maintainers or even new product owners for this project. Please let me know if you are interested in this project.

    Thanks,

    opened by gfx 0
  • How to set Locale when create database?

    How to set Locale when create database?

    When I try to create database the database has been created with system locale. For example, my system default language is Japanese. So the sqlite database create with locale ja_JP. It is fine with Insert, Update, Delete. When select using where and like operator, the result doesn't meet the requirement. Kanjis can't be extract via like keyword. Is there any option to select? Can you add option like setLocale(var locale : Locale) when build the database.

    opened by thawhtetkaung-private 0
  • Orma incompatible with Room?

    Orma incompatible with Room?

    Dear Orma developers,

    I've been hours banging my head against the wall so I decided to check with you. I have the impression that there is some sort of incompatibility between Orma and Android's new architecture component 'Room'.

    In my project I used to have Orma with a small DB (one table, really). I'm using the latest and greatest:

    annotationProcessor 'com.github.maskarade.android.orma:orma-processor:6.0.2'
    implementation 'com.github.maskarade.android.orma:orma:6.0.2'
    

    And I've just added Room. It all goes well until I add Room's processor:

    annotationProcessor "androidx.room:room-compiler:2.1.0"
    

    From that time on, the compiler complains it can't find OrmaDatabase:error: cannot find symbol class OrmaDatabase When it tries to compile the class that's using it. It fails when I try to declare the first variable that uses it OrmaDatabase db;

    I've tried everything: clean, rebuild, invalidate caches... I've even tried to copy the OrmaDatabase.java in the tmp/generated (along with the other files) with no success.

    Has anyone faced this issue? Can you help me somehow? Thanks!

    opened by naevtamarkus 0
Owner
The Maskarade project
To maintain Open Source Software for the Android platform with teams
The Maskarade project
AndroidQuery is an Android ORM for SQLite and ContentProvider which focuses on easy of use and performances thanks to annotation processing and code generation

WARNING: now that Room is out, I no longer maintain that library. If you need a library to easy access to default android ContentProvider, I would may

Frédéric Julian 19 Dec 11, 2021
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
A blazing fast, powerful, and very simple ORM android database library that writes database code for you.

README DBFlow is fast, efficient, and feature-rich Kotlin database library built on SQLite for Android. DBFlow utilizes annotation processing to gener

Andrew Grosner 4.9k Dec 30, 2022
lightweight and minimalist ORM for Java/Android. works with SQLite & MySQL. (not actively maintained)

Description ORMAN is an minimalistic and lightweight ORM framework for Java which can handle your common database usage without writing SQL and strugg

Ahmet Alp Balkan 246 Nov 20, 2022
lightweight and minimalist ORM for Java/Android. works with SQLite & MySQL. (not actively maintained)

Description ORMAN is an minimalistic and lightweight ORM framework for Java which can handle your common database usage without writing SQL and strugg

Ahmet Alp Balkan 246 Nov 20, 2022
greenDAO is a light & fast ORM solution for Android that maps objects to SQLite databases.

Check out ObjectBox Check out our new mobile database ObjectBox (GitHub repo). ObjectBox is a superfast object-oriented database with strong relation

Markus Junginger 12.6k Jan 3, 2023
Android ORM

Shillelagh Shillelagh is an sqlite library. It was built to make life easier. The entire library was built around simplicity when using sqlite in Andr

Andrew Reitz 49 Sep 11, 2020
Compile-time active record ORM for Android

Ollie Compile-time active record ORM for Android. Multiple mapping methods. SQLiteDatabase-like interface (QueryUtils.java). Lightweight query builder

Michael Pardo 423 Dec 30, 2022
ORMDroid is a simple ORM persistence framework for your Android applications.

ORMDroid is a simple ORM persistence framework for your Android applications, providing an easy to use, almost-zero-config way to handle model persist

Ross Bamford 87 Nov 10, 2022
LiteOrm is a fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line of code efficiently.

#LiteOrm:Android高性能数据库框架 A fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line

马天宇 1.5k Nov 19, 2022
Performance comparison of Android ORM Frameworks

Performance comparison of Android ORM Frameworks At the moment there are a lot of ORM-libraries for the Android OS. We reviewed the most popular ones

Alexey Zatsepin 328 Dec 21, 2022
a 3d database ORM experiment. (used in two commercial projects)

Android-TriOrm a 3d database ORM experiment for Android. (used in two commercial projects). based around small tables concept and JVM Serialization. H

Tomer Shalev 19 Nov 24, 2021
JAKO: Just Another Kotlin Orm (PostgreSQL)

JAKO: Just Another Kotlin Orm (PostgreSQL) JAKO is a simple, minimal, no-dependency library to build and execute postgresql statements using a fluent

Alessio 6 May 27, 2022
This is an Online Book App in which user can read and add their books on favourites fragment and also give rating on it.

BookHub-AndroidApp BookHub Basic Android App Based on the concept of Fragment, Navigation Drawer, Database (Room), Internet Access, etc. See the app o

Yash Kumar Shrivas 3 Mar 10, 2022
A simple NoSQL client for Android. Meant as a document store using key/value pairs and some rudimentary querying. Useful for avoiding the hassle of SQL code.

SimpleNoSQL A simple NoSQL client for Android. If you ever wanted to just save some data but didn't really want to worry about where it was going to b

Colin Miller 389 Sep 25, 2022
An Android helper class to manage database creation and version management using an application's raw asset files

THIS PROJECT IS NO LONGER MAINTAINED Android SQLiteAssetHelper An Android helper class to manage database creation and version management using an app

Jeff Gilfelt 2.2k Dec 23, 2022
SquiDB is a SQLite database library for Android and iOS

Most ongoing development is currently taking place on the dev_4.0 branch. Click here to see the latest changes and try out the 4.0 beta. Introducing S

Yahoo 1.3k Dec 26, 2022
Android library for auto generating SQL schema and Content provider

Android-AnnotatedSQL Android library for auto generating SQL schema and Content Provider by annotations. You will get a full-featured content provider

Gennadiy Dubina 161 Dec 3, 2022
A simple ToDo app to demonstrate the use of Realm Database in android to perform some basic CRUD operations like Create, Update and Delete.

Creating a Realm Model Class @RealmClass open class Note() : RealmModel { @PrimaryKey var id: String = "" @Required var title: String

Joel Kanyi 15 Dec 18, 2022