📜 ⬆️ ⬇️

Using Berkeley DB in an Android application

After successfully completing the “Hello World” stage for Android, I decided to write a simple Android application for interest, the main functionality of which was to store a certain set of data on the device. And I really did not want to work with SQL. I'm used to somehow working with objects. Therefore, having searched the Internet in search of solutions compatible with Android, I found only one thing - Berkeley DB, an embedded database.
Moreover, the documentation from Oracle showed significantly better performance in comparison with SQlite. Therefore, for my application (beyond my phone, it never went away) I chose this data storage format.
The class that is the core of work with the database is made on the template Singleton, and it turned 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 getting access to an entity, it must be initialized, which can be forgotten. Plus, the problem of creating / closing a transaction popped up. A transaction opens in one class, and closes in another, which also does not look the best way from a development point of view. So far this “mistake” I could not “beautifully” fix. This looks especially crooked in the light of the fact that transactions are used to obtain the next value of the identifier for the entity being saved.

At a higher level, DataAccess data access classes were created.
')
 public class FuelItemDA { private PrimaryIndex<Long, FuelItem> prIndex; private SecondaryIndex<Long, Long, FuelItem> odometerIndex; private SecondaryIndex<Date, Long, FuelItem> 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<FuelItem> getItemsInDates(Date bDate, Date eDate) { List<FuelItem> result = new LinkedList<FuelItem>(); EntityCursor<FuelItem> cursor = dateIndex.entities(bDate, true, eDate, true); for (Iterator<FuelItem> 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 it is necessary to pay attention to the creation of indexes, which are then searched and filtered. Those. if it becomes necessary to search and filter data by another set of fields, then it will be necessary to create an additional index.

Another feature of working with Berkley DB was writing the 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:


Personally, I liked this approach to organizing data storage. Since 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 here at all.
 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 advanced tool for accessing data in the form of SQL.
The advantages to working with Berkley Db include direct CRUD operations on objects, which facilitates the subsequent logical work with data. For me, it had more weight than the usual data output interface.

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

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


All Articles