Skip to content
svenmeier edited this page May 1, 2012 · 35 revisions

Working with Sqlite is very easy with propoid-db, create a repository and start right away without any setup:

    Repository repository = new Repositiory(context, "foos");

All required tables will automagically be created (or altered in case of new properties) once you perform operations on the repository.

Propoid supports many features commonly expected by an object-relational mapper:

  • arbitrary property types (provided adequate Mappers are registered)
  • inheritance
  • lazy loading of referenced propoids
  • transactions
  • optional object identity
  • typed queries
  • live query results backed by a database cursor

Modification

To insert a new object just pass it to the Repository:

    repository.insert(new Foo());

An object can similarly be updated:

    repository.update(foo);

Deletion is simple too:

    repository.delete(foo);

Queries

Objects can be queried with a fully typed API:

    Match<Foo> match = repository.query(Where.equal(new Foo().bar, "BAR"));

Note that a database operation is performed only after you invoke a method on the Match, e.g.:

    Foo foo = match.first(Order.ascending(new Foo().bar));

All query methods:

  • first() get the first match (if any)
  • single() get the single expected match (or null)
  • count() get count of matched Propoids
  • max(Property<T>) get the max value of a property
  • min(Property<T>) get the min value of a property
  • sum(Property<T>) get the sum of values of a property
  • avg(Property<T>) get the average value of a property
  • list(Order...) get all matches, optionally ordered by properties
  • list(Range, Order...) get a range of matches, optionally ordered by properties

The list() methods are special in that their elements are backed by a life database cursor. To close its resources you have to cast its result to java.io.Closable and call close() on it. Even simpler you can just consume all its elements, e.g.:

    for (Foo foo : foos) {
    }
    // the list is consumed now

Additionally you can perform bulk modifications on the matched Propoids:

  • set(Property<T>, T) set a property
  • delete() delete all matched Propoids

Reference

A Reference is used to refer to a specific Propoid instance and can be persisted or passed between activities:

    intent.setData(new Reference<Foo>(foo).toUri());

You can use references for intent filters too:

    <intent-filter>
        <action android:name="android.intent.action.EDIT"/>
        <category android:name="android.intent.category.DEFAULT" />
        <data
            android:scheme="propoid"
            android:host="your.package.Foo"
        /> 
    </intent-filter>

The propoid can then be looked up in your activitiy:

    Foo foo = repository.lookup(Reference.fromUri(intent.getData()));

Indices

To increase performance of your application you might want to add indices to your repository. For example the following creates a non-unique index on Foo by ascending bar:

    Foo foo = new Foo();
    repository.index(foo, false, Order.ascending(foo.bar));

Locator

A Locator specifies how the SQLiteDatabase is openened:

  • InMemoryLocator opens a database in memory
  • FileLocator (the default) opens a database file, either speficied explicitely or relative to the application context

FileLocator allows initializing the database content on creation:

    new FileLocator(context, "foos") {
        protected void initial(File file) {
            copy(context.getAssets().open("default-foos"), new FileOutputStream(file));
        }
    };

Settings

Each repository is highly configurable through a set of settings, e.g.:

    Repository repository = new Repository(context, "foos", new FoosMapping(), new FoosFactory());

Mapping

Mapping provides Mappers which are used to map properties to database columns. DefaultMapping handles most common types:

  • primitives and their wrapper classes
  • Strings
  • byte arrays
  • java.util.Locale
  • java.util.Date
  • android.location.Location
  • enums
  • types (i.e. class instances)
  • relations to propoids

Naming

Naming decides which table to be used for each Propoid and how to encode subclasses. DefaultNaming uses a single-table-mapping for inheritance.

Factory

All Propoids read from the database are created by a factory. DefaultFactory creates a new instance on each requested Propoid.

Cascading

Before Propoids are inserted, updated or deleted, this strategy is allowed to cascade operations to referenced Propoids. DefaultCascading allows registering of properties, which should be cascaded.

Versioning

On opening the database this strategy allows to perform required upgrades.

DefaultVersioning keeps a list of Upgrades, each being able to upgrade a database to the subsequent version:

    DefaultVersioning versioning = new DefaultVersioning();
    versioning.add(new SQLExec(new SQL("alter table foo drop column bar"))); // update to version 1
    versioning.add(new SQLExec(new SQL("drop table foo"))); // update to version 2

The current database's version is tracked so that upgrades are applied automagically.

Deriving a repository

A repository can be derived with custom settings as needed.

In the following example we want the repository to keep object identify for already loaded objects, thus we derive with an IdentityFactory passing in the old objects:

    Factory factory = new IdentityFactory(oldFoos, repository.factory);

    List<Foo> newFoos = repository.derive(factory).query(new Foo()).list();

Note that all objects already present in oldFoos are now reused for the returned match.

Service

A repository can easily run as a service. Just register RepositoryService or a subclass in your application manifest and then bind to a RepositoryConnection in your activities or services:

    new RepositoryConnection() {
      public void onConnected(Repository repository) {
        // ... work with repository
      }
    }.bind(context, RepositoryService.class);

A repository can be used for search suggestions too: Register a subclass of RepositorySuggest in your application manifest:

    public class FooRepositorySuggest extends RespositorySuggest<Foo> {

      protected Match<Foo> query(Repository repository, String query) {
        return repository.query(Where.like(new Foo().bar, "%" + query + "%"));
      }

      // what text should be used for displaying search results
      protected String getText1(Foo foo);
        return foo.bar.get()
      }
    }

Then implement your search activity:

    public void onCreate(Bundle state) {
      super.onCreate(state);

      if (Intent.ACTION_SEARCH.equals(getIntent().getAction())) {
        String query = RepositorySuggest.getQuery(getIntent());
      } else if (Intent.ACTION_VIEW.equals(getIntent().getAction())) {
        Reference<Foo> reference = RepositorySuggest.getReference(getIntent());
      } else {
        // other action
      }
    }

Limitations

To keep things simple propoid-db is missing some features to be a full-blown OR mapper:

  • query results can not be grouped
  • only one aggregate function can be applied on each query
  • no fetch joins
  • one-to-many relations are not supported