Using Berkeley DB in an Android app

After the successfully completed “Hello World” stage for Android, I decided to write for fun a simple Android application, the main functionality of which was to store some data set on the device. And I really did not want to work with SQL. I'm used to working with objects somehow. Therefore, after searching the Internet in search of compatible Android solutions, I found only one thing - Berkeley DB, an embedded database.
Moreover, the documentation from Oracle showed significantly better performance indicators compared to SQlite. Therefore, I chose this data storage format for my application (it didn’t go further than my phone).
The class which is the core of working with the database is made using the Singleton template, and it turns out as follows:

public class DatabaseConfig {
    private static DatabaseConfig ourInstance;
    private Environment envmnt;
    private EntityStore store;
    public static DatabaseConfig getInstance() {
        if (ourInstance == null)
            throw new IllegalArgumentException("You need initialize database config previously!");
        return ourInstance;
    }
    public static void init(File envDir) {
        ourInstance = new DatabaseConfig(envDir);
    }
    private DatabaseConfig(File envDir) {
        EnvironmentConfig envConfig = new EnvironmentConfig();
        StoreConfig storeConfig = new StoreConfig();
        envConfig.setTransactional(true);
        envConfig.setAllowCreate(true);
        storeConfig.setAllowCreate(true);
        storeConfig.setTransactional(true);
        envmnt = new Environment(envDir, envConfig);
        try {
            store = new EntityStore(envmnt, "autocalc", storeConfig);
        } catch (IncompatibleClassException e) {
            //todo: реализовать преобразования данных.
        }
    }
    public static void shutdown() {
        if (ourInstance != null) {
            ourInstance.close();
        }
    }
    private void close() {
        store.close();
        envmnt.close();
    }
    public EntityStore getStore() {
        return store;
    }
    public Transaction startTransaction() {
        return envmnt.beginTransaction(null, null);
    }
}


The problems of this class are quite prosaic, before accessing an entity, it must be initialized, which can be forgotten. Plus, the problem of creating / closing a transaction popped up. A transaction is opened in one class, and closed in another, which also does not look the best from the point of view of development. So far, I have not been able to “beautifully” correct this “mistake”. This looks especially crooked in light of the fact that transactions are used to get the next identifier value for a stored entity.

At a higher level, DataAccess data access classes have been created.

public class FuelItemDA {
    private PrimaryIndex prIndex;
    private SecondaryIndex odometerIndex;
    private SecondaryIndex dateIndex;
    private DatabaseConfig dbConfig;
    public FuelItemDA() {
        dbConfig = DatabaseConfig.getInstance();
        prIndex = dbConfig.getStore().getPrimaryIndex(
                Long.class, FuelItem.class);
        odometerIndex = dbConfig.getStore().getSecondaryIndex(
                prIndex, Long.class, "odometer");
        dateIndex = dbConfig.getStore().getSecondaryIndex(
                prIndex, Date.class, "operationDate");
    }
    public void save(FuelItem item) {
        Transaction tx = dbConfig.startTransaction();
        try {
            if (item.getId() == 0) {
                long id = dbConfig.getStore().getSequence("SPENT_ID").get(tx, 1);
                item.setId(id);
            }
            prIndex.put(tx, item);
            tx.commit();
        } catch (Exception e) {
            e.printStackTrace();
            if (tx != null) {
                tx.abort();
                tx = null;
            }
        }
    }
    public FuelItem load(long id) {
        return prIndex.get(id);
    }
    public List getItemsInDates(Date bDate, Date eDate) {
        List result = new LinkedList();
        EntityCursor cursor = dateIndex.entities(bDate, true, eDate, true);
        for (Iterator iterator = cursor.iterator(); iterator.hasNext(); ) {
            FuelItem spentItem = iterator.next();
            result.add(spentItem);
        }
        cursor.close();
        return result;
    }
    public void removeFuelItem(long id) {
        try {
            prIndex.delete(id);
        } catch (DatabaseException e) {
            e.printStackTrace();
            prIndex.delete(id);
        }
    }
}


Here you need to pay attention to the creation of indexes, which are then used to search and filter. Those. if you need to search and filter data by another set of fields, you will need to create an additional index.

Another feature of working with Berkley DB was the writing of entity classes that are used to store information. As planned, Berkley DB was able to store a hierarchy of objects.

@Persistent(version = 1)
public class SpentItem implements Item{
    @PrimaryKey(sequence="SPENT_ID")
    private long id;
    @SecondaryKey(relate= Relationship.MANY_TO_ONE)
    private long odometer;
    @SecondaryKey(relate= Relationship.MANY_TO_ONE)
    private Date operationDate;
    private double sum;
....
}
@Entity(version = 1)
public class FuelItem extends SpentItem {
    private double count;
    private double price;
    private boolean full;
.....
}


In entity classes, information is transmitted through annotations:
  • about the version of the structure of objects that should now be stored in the database. If the structure of the object changes, then you need to write a translator that translates the data structure from an earlier version to the current one. I solved the migration problem through a try / catch block in the FuelItemDA constructor.
  • Primary and Secondory keys, which are then used to build indexes, which I have defined at the DataAccess level


Personally, I liked this approach to organizing data storage. Because For display, I need not so much data that is stored in the database, but logically processed, which is easier to do with objects.

It remains only to initialize DatabaseConfig, and there are no difficulties whatsoever.
public class Calc extends Activity {
    private void setup() throws DatabaseException {
        File envDir = new File(android.os.Environment.getExternalStorageDirectory(), "data");
        envDir = new File(envDir, "autoexpence");
        if (!envDir.exists()) {
            if (!envDir.mkdirs()) {
                Log.e("TravellerLog :: ", "Problem creating Image folder");
            }
        }
        DatabaseConfig.init(envDir);
    }
}


The advantages to working with SQlite include the familiar and more developed tools for accessing data in the form of SQL.
The advantages of working with Berkley Db include direct CRUD operations on objects, which facilitates subsequent logical work with data. For me, this carried more weight than the usual data output interface.

PS
Download link. Need
Berkeley DB Java version . Inside the archive you will find the library for Android.

Also popular now: